Browse Source

Merge pull request #57 from ammargitham/feature/share-photos-dm

Feature/share photos dm
legacy
Austin Huang 4 years ago
committed by GitHub
parent
commit
e5c0b376f6
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .gitignore
  2. 7
      app/build.gradle
  3. 11
      app/src/main/AndroidManifest.xml
  4. 11
      app/src/main/java/awais/instagrabber/InstaApp.java
  5. 118
      app/src/main/java/awais/instagrabber/activities/DirectMessages.java
  6. 73
      app/src/main/java/awais/instagrabber/activities/DirectMessagesActivity.java
  7. 269
      app/src/main/java/awais/instagrabber/activities/DirectMessagesUserInbox.java
  8. 31
      app/src/main/java/awais/instagrabber/activities/Main.java
  9. 54
      app/src/main/java/awais/instagrabber/adapters/DirectMessageInboxAdapter.java
  10. 131
      app/src/main/java/awais/instagrabber/adapters/DirectMessagesAdapter.java
  11. 400
      app/src/main/java/awais/instagrabber/adapters/MessageItemsAdapter.java
  12. 107
      app/src/main/java/awais/instagrabber/adapters/viewholder/DirectMessageInboxItemViewHolder.java
  13. 43
      app/src/main/java/awais/instagrabber/adapters/viewholder/DirectMessageViewHolder.java
  14. 383
      app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageViewHolder.java
  15. 91
      app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/TextMessageViewHolder.java
  16. 176
      app/src/main/java/awais/instagrabber/asyncs/ImageUploader.java
  17. 32
      app/src/main/java/awais/instagrabber/asyncs/direct_messages/DirectMessageInboxThreadFetcher.java
  18. 207
      app/src/main/java/awais/instagrabber/asyncs/direct_messages/DirectThreadBroadcaster.java
  19. 149
      app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageInboxFragment.java
  20. 351
      app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java
  21. 62
      app/src/main/java/awais/instagrabber/models/ImageUploadOptions.java
  22. 24
      app/src/main/java/awais/instagrabber/models/ProfileModel.java
  23. 18
      app/src/main/java/awais/instagrabber/models/direct_messages/InboxThreadModel.java
  24. 14
      app/src/main/java/awais/instagrabber/models/enums/UserInboxDirection.java
  25. 53
      app/src/main/java/awais/instagrabber/utils/Utils.java
  26. 10
      app/src/main/res/drawable/ic_image_24.xml
  27. 10
      app/src/main/res/drawable/ic_send_24.xml
  28. 54
      app/src/main/res/layout/activity_direct_messages.xml
  29. 10
      app/src/main/res/layout/fragment_direct_messages_inbox.xml
  30. 48
      app/src/main/res/layout/fragment_direct_messages_thread.xml
  31. 3
      app/src/main/res/layout/item_message_item.xml
  32. 26
      app/src/main/res/navigation/direct_messages_nav_graph.xml
  33. 3
      app/src/main/res/values/strings.xml
  34. 3
      build.gradle

2
.gitignore

@ -14,4 +14,4 @@
/captures /captures
.externalNativeBuild .externalNativeBuild
.cxx .cxx
app/release/
app/release

7
app/build.gradle

@ -1,4 +1,5 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: "androidx.navigation.safeargs"
android { android {
compileSdkVersion 29 compileSdkVersion 29
@ -37,10 +38,14 @@ android {
dependencies { dependencies {
implementation('androidx.appcompat:appcompat:1.3.0-alpha01@aar') { transitive true } implementation('androidx.appcompat:appcompat:1.3.0-alpha01@aar') { transitive true }
implementation('androidx.recyclerview:recyclerview:1.2.0-alpha03@aar') { transitive true }
implementation "androidx.recyclerview:recyclerview:1.1.0"
implementation('com.google.android.material:material:1.3.0-alpha01@aar') { transitive true } implementation('com.google.android.material:material:1.3.0-alpha01@aar') { transitive true }
implementation('androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-rc01') { transitive true } implementation('androidx.swiperefreshlayout:swiperefreshlayout:1.1.0-rc01') { transitive true }
def nav_version = "2.3.0"
implementation "androidx.navigation:navigation-fragment:$nav_version"
implementation "androidx.navigation:navigation-ui:$nav_version"
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0' annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
implementation('org.jsoup:jsoup:1.13.1') { transitive true } implementation('org.jsoup:jsoup:1.13.1') { transitive true }

11
app/src/main/AndroidManifest.xml

@ -215,7 +215,7 @@
</activity> </activity>
<activity <activity
android:name=".activities.DirectMessages"
android:name=".activities.DirectMessagesActivity"
android:parentActivityName=".activities.Main"> android:parentActivityName=".activities.Main">
<meta-data <meta-data
@ -223,15 +223,6 @@
android:value=".activities.Main" /> android:value=".activities.Main" />
</activity> </activity>
<activity
android:name=".activities.DirectMessagesUserInbox"
android:parentActivityName=".activities.DirectMessages">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.DirectMessages" />
</activity>
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider" android:authorities="${applicationId}.provider"

11
app/src/main/java/awais/instagrabber/InstaApp.java

@ -2,6 +2,7 @@ package awais.instagrabber;
import android.content.ClipboardManager; import android.content.ClipboardManager;
import android.content.Context; import android.content.Context;
import android.util.Log;
import androidx.core.app.NotificationManagerCompat; import androidx.core.app.NotificationManagerCompat;
import androidx.multidex.MultiDexApplication; import androidx.multidex.MultiDexApplication;
@ -32,6 +33,16 @@ public final class InstaApp extends MultiDexApplication {
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
if (BuildConfig.DEBUG) {
try {
Class.forName("dalvik.system.CloseGuard")
.getMethod("setEnabled", boolean.class)
.invoke(null, true);
} catch (Exception e) {
Log.e("InstaApp", "Error", e);
}
}
if (!BuildConfig.DEBUG) CrashReporter.get(this).start(); if (!BuildConfig.DEBUG) CrashReporter.get(this).start();
logCollector = new LogCollector(this); logCollector = new LogCollector(this);

118
app/src/main/java/awais/instagrabber/activities/DirectMessages.java

@ -1,118 +0,0 @@
package awais.instagrabber.activities;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import android.view.View;
import java.util.ArrayList;
import java.util.Arrays;
import awais.instagrabber.R;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.adapters.DirectMessagesAdapter;
import awais.instagrabber.asyncs.direct_messages.InboxFetcher;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.databinding.ActivityDmsBinding;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.direct_messages.InboxModel;
import awais.instagrabber.models.direct_messages.InboxThreadModel;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
public final class DirectMessages extends BaseLanguageActivity implements SwipeRefreshLayout.OnRefreshListener {
private final ArrayList<InboxThreadModel> inboxThreadModelList = new ArrayList<>();
private final DirectMessagesAdapter messagesAdapter = new DirectMessagesAdapter(inboxThreadModelList, v -> {
final Object tag = v.getTag();
if (tag instanceof InboxThreadModel) {
startActivity(new Intent(this, DirectMessagesUserInbox.class)
.putExtra(Constants.EXTRAS_THREAD_MODEL, (InboxThreadModel) tag)
);
}
});
private final FetchListener<InboxModel> fetchListener = new FetchListener<InboxModel>() {
@Override
public void doBefore() {
dmsBinding.swipeRefreshLayout.setRefreshing(true);
}
@Override
public void onResult(final InboxModel inboxModel) {
if (inboxModel != null) {
endCursor = inboxModel.getOldestCursor();
if ("MINCURSOR".equals(endCursor) || "MAXCURSOR".equals(endCursor)) endCursor = null;
// todo get request / unseen count from inboxModel
final InboxThreadModel[] threads = inboxModel.getThreads();
if (threads != null && threads.length > 0) {
final int oldSize = inboxThreadModelList.size();
inboxThreadModelList.addAll(Arrays.asList(threads));
messagesAdapter.notifyItemRangeInserted(oldSize, threads.length);
}
}
dmsBinding.swipeRefreshLayout.setRefreshing(false);
stopCurrentExecutor();
}
};
private String endCursor;
private RecyclerLazyLoader lazyLoader;
private AsyncTask<Void, Void, InboxModel> currentlyRunning;
private ActivityDmsBinding dmsBinding;
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
dmsBinding = ActivityDmsBinding.inflate(getLayoutInflater());
setContentView(dmsBinding.getRoot());
dmsBinding.swipeRefreshLayout.setOnRefreshListener(this);
dmsBinding.toolbar.toolbar.setTitle(R.string.action_dms);
dmsBinding.commentText.setVisibility(View.GONE);
dmsBinding.commentSend.setVisibility(View.GONE);
final LinearLayoutManager layoutManager = new LinearLayoutManager(this);
dmsBinding.rvDirectMessages.setLayoutManager(layoutManager);
dmsBinding.rvDirectMessages.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
dmsBinding.rvDirectMessages.setAdapter(messagesAdapter);
lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
if (!Utils.isEmpty(endCursor))
currentlyRunning = new InboxFetcher(endCursor, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
endCursor = null;
});
dmsBinding.rvDirectMessages.addOnScrollListener(lazyLoader);
stopCurrentExecutor();
currentlyRunning = new InboxFetcher(null, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@Override
public void onRefresh() {
endCursor = null;
lazyLoader.resetState();
inboxThreadModelList.clear();
messagesAdapter.notifyDataSetChanged();
stopCurrentExecutor();
currentlyRunning = new InboxFetcher(null, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void stopCurrentExecutor() {
if (currentlyRunning != null) {
try {
currentlyRunning.cancel(true);
} catch (final Exception e) {
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
}
}
}
}

73
app/src/main/java/awais/instagrabber/activities/DirectMessagesActivity.java

@ -0,0 +1,73 @@
package awais.instagrabber.activities;
import android.os.Bundle;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.Toolbar;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.navigation.NavController;
import androidx.navigation.NavDestination;
import androidx.navigation.Navigation;
import androidx.navigation.ui.AppBarConfiguration;
import androidx.navigation.ui.NavigationUI;
import awais.instagrabber.R;
import awais.instagrabber.databinding.ActivityDirectMessagesBinding;
import awais.instagrabber.fragments.directmessages.DirectMessageThreadFragmentArgs;
public class DirectMessagesActivity extends BaseLanguageActivity implements NavController.OnDestinationChangedListener {
private TextView toolbarTitle;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final ActivityDirectMessagesBinding binding = ActivityDirectMessagesBinding.inflate(getLayoutInflater());
final CoordinatorLayout root = binding.getRoot();
setContentView(root);
toolbarTitle = binding.toolbarTitle;
final Toolbar toolbar = binding.toolbar;
setSupportActionBar(toolbar);
final NavController navController = Navigation.findNavController(this, R.id.direct_messages_nav_host_fragment);
navController.addOnDestinationChangedListener(this);
final AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(navController.getGraph()).build();
NavigationUI.setupWithNavController(toolbar, navController, appBarConfiguration);
}
@Override
public void onDestinationChanged(@NonNull final NavController controller,
@NonNull final NavDestination destination,
@Nullable final Bundle arguments) {
switch (destination.getId()) {
case R.id.directMessagesInboxFragment:
setToolbarTitle(R.string.action_dms);
return;
case R.id.directMessagesThreadFragment:
if (arguments == null) {
return;
}
final String title = DirectMessageThreadFragmentArgs.fromBundle(arguments).getTitle();
setToolbarTitle(title);
return;
}
}
private void setToolbarTitle(final String text) {
if (toolbarTitle == null) {
return;
}
toolbarTitle.setText(text);
}
private void setToolbarTitle(final int resourceId) {
if (toolbarTitle == null) {
return;
}
toolbarTitle.setText(resourceId);
}
}

269
app/src/main/java/awais/instagrabber/activities/DirectMessagesUserInbox.java

@ -1,269 +0,0 @@
package awais.instagrabber.activities;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.io.DataOutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import awais.instagrabber.R;
import awais.instagrabber.adapters.MessageItemsAdapter;
import awais.instagrabber.asyncs.direct_messages.UserInboxFetcher;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.databinding.ActivityDmsBinding;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.direct_messages.DirectItemModel;
import awais.instagrabber.models.direct_messages.InboxThreadModel;
import awais.instagrabber.models.enums.DirectItemType;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.UserInboxDirection;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Utils.settingsHelper;
public final class DirectMessagesUserInbox extends BaseLanguageActivity {
private DirectItemModel directItemModel;
private final ProfileModel myProfileHolder =
new ProfileModel(false, false, false, null, null, null, null, null, null, null, 0, 0, 0, false, false, false, false);
private final ArrayList<ProfileModel> users = new ArrayList<>(), leftusers = new ArrayList<>();
private final ArrayList<DirectItemModel> directItemModels = new ArrayList<>();
private String threadid;
private final FetchListener<InboxThreadModel> fetchListener = new FetchListener<InboxThreadModel>() {
@Override
public void doBefore() {
dmsBinding.swipeRefreshLayout.setRefreshing(true);
}
@Override
public void onResult(final InboxThreadModel result) {
if (result == null && ("MINCURSOR".equals(endCursor) || "MAXCURSOR".equals(endCursor) || Utils.isEmpty(endCursor)))
Toast.makeText(DirectMessagesUserInbox.this, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
if (result != null) {
endCursor = result.getPrevCursor();
if ("MINCURSOR".equals(endCursor) || "MAXCURSOR".equals(endCursor)) endCursor = null;
users.clear();
users.addAll(Arrays.asList(result.getUsers()));
leftusers.clear();
leftusers.addAll(Arrays.asList(result.getLeftUsers()));
threadid = result.getThreadId();
dmsBinding.toolbar.toolbar.setTitle(result.getThreadTitle());
String[] users = new String[result.getUsers().length];
for (int i = 0; i < users.length; ++i) {
users[i] = result.getUsers()[i].getUsername();
}
dmsBinding.toolbar.toolbar.setSubtitle(String.join(", ", users));
final int oldSize = directItemModels.size();
final List<DirectItemModel> itemModels = Arrays.asList(result.getItems());
directItemModels.addAll(itemModels);
messageItemsAdapter.notifyItemRangeInserted(oldSize, itemModels.size());
}
dmsBinding.swipeRefreshLayout.setRefreshing(false);
}
};
private String endCursor;
private ActivityDmsBinding dmsBinding;
private MessageItemsAdapter messageItemsAdapter;
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
dmsBinding = ActivityDmsBinding.inflate(getLayoutInflater());
setContentView(dmsBinding.getRoot());
final InboxThreadModel threadModel;
final Intent intent = getIntent();
if (intent == null || !intent.hasExtra(Constants.EXTRAS_THREAD_MODEL) ||
(threadModel = (InboxThreadModel) intent.getSerializableExtra(Constants.EXTRAS_THREAD_MODEL)) == null) {
Utils.errorFinish(this);
return;
}
dmsBinding.swipeRefreshLayout.setEnabled(false);
dmsBinding.commentText.setVisibility(View.VISIBLE);
dmsBinding.commentSend.setVisibility(View.VISIBLE);
dmsBinding.commentSend.setOnClickListener(newCommentListener);
final LinearLayoutManager layoutManager = new LinearLayoutManager(this, RecyclerView.VERTICAL, true);
dmsBinding.rvDirectMessages.setLayoutManager(layoutManager);
dmsBinding.rvDirectMessages.addOnScrollListener(new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
if (!Utils.isEmpty(endCursor)) {
new UserInboxFetcher(threadModel.getThreadId(), UserInboxDirection.OLDER,
endCursor, fetchListener).execute(); // serial because we don't want messages to be randomly ordered
}
}));
messageItemsAdapter = new MessageItemsAdapter(directItemModels, users, leftusers, v -> {
Object tag = v.getTag();
if (tag instanceof DirectItemModel) {
directItemModel = (DirectItemModel) tag;
final DirectItemType itemType = directItemModel.getItemType();
switch (itemType) {
case MEDIA_SHARE:
startActivity(new Intent(this, PostViewer.class)
.putExtra(Constants.EXTRAS_POST, new PostModel(directItemModel.getMediaModel().getCode(), false)));
break;
case LINK:
Intent linkIntent = new Intent(Intent.ACTION_VIEW);
linkIntent.setData(Uri.parse(directItemModel.getLinkModel().getLinkContext().getLinkUrl()));
startActivity(linkIntent);
break;
case TEXT:
case REEL_SHARE:
Utils.copyText(v.getContext(), directItemModel.getText());
Toast.makeText(v.getContext(), R.string.clipboard_copied, Toast.LENGTH_SHORT).show();
break;
case RAVEN_MEDIA:
case MEDIA:
Utils.dmDownload(this, getUser(directItemModel.getUserId()).getUsername(), DownloadMethod.DOWNLOAD_DIRECT,
Collections.singletonList(itemType == DirectItemType.MEDIA ? directItemModel.getMediaModel() : directItemModel.getRavenMediaModel().getMedia()));
Toast.makeText(v.getContext(), R.string.downloader_downloading_media, Toast.LENGTH_SHORT).show();
break;
case STORY_SHARE:
if (directItemModel.getReelShare() != null) {
StoryModel sm = new StoryModel(
directItemModel.getReelShare().getReelId(),
directItemModel.getReelShare().getMedia().getVideoUrl(),
directItemModel.getReelShare().getMedia().getMediaType(),
directItemModel.getTimestamp(),
directItemModel.getReelShare().getReelOwnerName(),
String.valueOf(directItemModel.getReelShare().getReelOwnerId()),
false
);
sm.setVideoUrl(directItemModel.getReelShare().getMedia().getVideoUrl());
StoryModel[] sms = {sm};
startActivity(new Intent(this, StoryViewer.class)
.putExtra(Constants.EXTRAS_USERNAME, directItemModel.getReelShare().getReelOwnerName())
.putExtra(Constants.EXTRAS_STORIES, sms)
);
}
else if (directItemModel.getText() != null && directItemModel.getText().toString().contains("@")) {
searchUsername(directItemModel.getText().toString().split("@")[1].split(" ")[0]);
}
break;
case PLACEHOLDER:
if (directItemModel.getText().toString().contains("@"))
searchUsername(directItemModel.getText().toString().split("@")[1].split(" ")[0]);
break;
default:
Log.d("austin_debug", "unsupported type "+itemType);
}
}
},
(view, text, isHashtag) -> {
searchUsername(text);
});
dmsBinding.rvDirectMessages.setAdapter(messageItemsAdapter);
new UserInboxFetcher(threadModel.getThreadId(), UserInboxDirection.OLDER, null, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@Nullable
private ProfileModel getUser(final long userId) {
if (users != null) {
ProfileModel result = myProfileHolder;
for (final ProfileModel user : users) {
if (Long.toString(userId).equals(user.getId())) result = user;
}
if (leftusers != null)
for (final ProfileModel leftuser : leftusers) {
if (Long.toString(userId).equals(leftuser.getId())) result = leftuser;
}
return result;
}
return null;
}
private void searchUsername(final String text) {
startActivity(new Intent(getApplicationContext(), ProfileViewer.class).putExtra(Constants.EXTRAS_USERNAME, text));
}
private final View.OnClickListener newCommentListener = v -> {
if (Utils.isEmpty(dmsBinding.commentText.getText().toString()) && v == dmsBinding.commentSend)
Toast.makeText(getApplicationContext(), R.string.comment_send_empty_comment, Toast.LENGTH_SHORT).show();
else if (v == dmsBinding.commentSend) new CommentAction().execute();
};
class CommentAction extends AsyncTask<Void, Void, Void> {
boolean ok = false;
protected Void doInBackground(Void... lmao) {
final String url2 = "https://i.instagram.com/api/v1/direct_v2/threads/broadcast/text/";
final String cookie = settingsHelper.getString(Constants.COOKIE);
try {
final HttpURLConnection urlConnection2 = (HttpURLConnection) new URL(url2).openConnection();
urlConnection2.setRequestMethod("POST");
urlConnection2.setRequestProperty("User-Agent", Constants.I_USER_AGENT);
urlConnection2.setUseCaches(false);
final String commentText = URLEncoder.encode(dmsBinding.commentText.getText().toString(), "UTF-8")
.replaceAll("\\+", "%20").replaceAll("\\%21", "!").replaceAll("\\%27", "'")
.replaceAll("\\%28", "(").replaceAll("\\%29", ")").replaceAll("\\%7E", "~");
final String cc = UUID.randomUUID().toString();
final String urlParameters2 = Utils.sign("{\"_csrftoken\":\"" + cookie.split("csrftoken=")[1].split(";")[0]
+"\",\"_uid\":\"" + Utils.getUserIdFromCookie(cookie)
+"\",\"__uuid\":\"" + settingsHelper.getString(Constants.DEVICE_UUID)
+"\",\"client_context\":\"" + cc
+"\",\"mutation_token\":\"" + cc
+"\",\"text\":\"" + commentText
+"\",\"thread_ids\":\"["+threadid
+"]\",\"action\":\"send_item\"}");
urlConnection2.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
urlConnection2.setRequestProperty("Content-Length", "" + Integer.toString(urlParameters2.getBytes().length));
urlConnection2.setDoOutput(true);
DataOutputStream wr2 = new DataOutputStream(urlConnection2.getOutputStream());
wr2.writeBytes(urlParameters2);
wr2.flush();
wr2.close();
urlConnection2.connect();
Log.d("austin_debug", urlConnection2.getResponseCode() + " " + urlParameters2 + " " + cookie);
if (urlConnection2.getResponseCode() == HttpURLConnection.HTTP_OK) {
ok = true;
}
urlConnection2.disconnect();
} catch (Throwable ex) {
Log.e("austin_debug", "dm send: " + ex);
}
return null;
}
@Override
protected void onPostExecute(Void result) {
if (!ok) Toast.makeText(getApplicationContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
else {
dmsBinding.commentText.setText("");
dmsBinding.commentText.clearFocus();
directItemModels.clear();
messageItemsAdapter.notifyDataSetChanged();
new UserInboxFetcher(threadid, UserInboxDirection.OLDER, null, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
}
}

31
app/src/main/java/awais/instagrabber/activities/Main.java

@ -85,7 +85,8 @@ public final class Main extends BaseLanguageActivity {
.putExtra(Constants.EXTRAS_HIGHLIGHT, highlightModel.getTitle()) .putExtra(Constants.EXTRAS_HIGHLIGHT, highlightModel.getTitle())
.putExtra(Constants.EXTRAS_STORIES, result) .putExtra(Constants.EXTRAS_STORIES, result)
); );
else Toast.makeText(Main.this, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
else
Toast.makeText(Main.this, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} }
} }
@ -142,7 +143,7 @@ public final class Main extends BaseLanguageActivity {
if (mainHelper != null && !Utils.isEmpty(result)) { if (mainHelper != null && !Utils.isEmpty(result)) {
closeAnyOpenDrawer(); closeAnyOpenDrawer();
addToStack(); addToStack();
userQuery = (result.contains("/") || result.startsWith("#") || result.startsWith("@")) ? result : ("@"+result);
userQuery = (result.contains("/") || result.startsWith("#") || result.startsWith("@")) ? result : ("@" + result);
mainHelper.onRefresh(); mainHelper.onRefresh();
} }
}; };
@ -153,7 +154,8 @@ public final class Main extends BaseLanguageActivity {
if (!Utils.isEmpty(username)) { if (!Utils.isEmpty(username)) {
if (!BuildConfig.DEBUG) { if (!BuildConfig.DEBUG) {
userQuery = username; userQuery = username;
if (mainHelper != null && !mainBinding.profileView.swipeRefreshLayout.isRefreshing()) mainHelper.onRefresh();
if (mainHelper != null && !mainBinding.profileView.swipeRefreshLayout.isRefreshing())
mainHelper.onRefresh();
} }
// adds cookies to database for quick access // adds cookies to database for quick access
cookieModel = Utils.dataBox.getCookie(uid); cookieModel = Utils.dataBox.getCookie(uid);
@ -167,7 +169,7 @@ public final class Main extends BaseLanguageActivity {
final String username = cookieModel.getUsername(); final String username = cookieModel.getUsername();
if (username != null) { if (username != null) {
found = true; found = true;
fetchListener.onResult("@"+username);
fetchListener.onResult("@" + username);
} }
} }
@ -198,8 +200,8 @@ public final class Main extends BaseLanguageActivity {
intent = new Intent(this, ProfilePicViewer.class).putExtra( intent = new Intent(this, ProfilePicViewer.class).putExtra(
((hashtagModel != null) ? Constants.EXTRAS_HASHTAG : (locationModel != null ? Constants.EXTRAS_LOCATION : Constants.EXTRAS_PROFILE)), ((hashtagModel != null) ? Constants.EXTRAS_HASHTAG : (locationModel != null ? Constants.EXTRAS_LOCATION : Constants.EXTRAS_PROFILE)),
((hashtagModel != null) ? hashtagModel : (locationModel != null ? locationModel : profileModel))); ((hashtagModel != null) ? hashtagModel : (locationModel != null ? locationModel : profileModel)));
}
else intent = new Intent(this, StoryViewer.class).putExtra(Constants.EXTRAS_USERNAME, userQuery.replace("@", ""))
} else
intent = new Intent(this, StoryViewer.class).putExtra(Constants.EXTRAS_USERNAME, userQuery.replace("@", ""))
.putExtra(Constants.EXTRAS_STORIES, storyModels) .putExtra(Constants.EXTRAS_STORIES, storyModels)
.putExtra(Constants.EXTRAS_HASHTAG, (hashtagModel != null)); .putExtra(Constants.EXTRAS_HASHTAG, (hashtagModel != null));
startActivity(intent); startActivity(intent);
@ -240,7 +242,8 @@ public final class Main extends BaseLanguageActivity {
mainBinding.profileView.privatePage2.setText(mainHelper.isLoggedIn ? R.string.no_acc_logged_in : R.string.no_acc); mainBinding.profileView.privatePage2.setText(mainHelper.isLoggedIn ? R.string.no_acc_logged_in : R.string.no_acc);
mainBinding.profileView.privatePage.setVisibility(View.VISIBLE); mainBinding.profileView.privatePage.setVisibility(View.VISIBLE);
} }
if (!mainBinding.profileView.swipeRefreshLayout.isRefreshing() && userQuery != null) mainHelper.onRefresh();
if (!mainBinding.profileView.swipeRefreshLayout.isRefreshing() && userQuery != null)
mainHelper.onRefresh();
mainHelper.onIntent(getIntent()); mainHelper.onIntent(getIntent());
} }
@ -300,7 +303,7 @@ public final class Main extends BaseLanguageActivity {
if (item == downloadAction) if (item == downloadAction)
downloadSelectedItems(); downloadSelectedItems();
else if (item == dmsAction) else if (item == dmsAction)
startActivity(new Intent(this, DirectMessages.class));
startActivity(new Intent(this, DirectMessagesActivity.class));
else if (item == notifAction) else if (item == notifAction)
startActivity(new Intent(this, NotificationsViewer.class)); startActivity(new Intent(this, NotificationsViewer.class));
else if (item == settingsAction) else if (item == settingsAction)
@ -335,7 +338,7 @@ public final class Main extends BaseLanguageActivity {
searchView.setQueryHint(getResources().getString(R.string.action_search)); searchView.setQueryHint(getResources().getString(R.string.action_search));
searchView.setSuggestionsAdapter(suggestionAdapter); searchView.setSuggestionsAdapter(suggestionAdapter);
searchView.setOnSearchClickListener(v -> { searchView.setOnSearchClickListener(v -> {
searchView.setQuery((cookieModel != null && userQuery != null && userQuery.equals("@"+cookieModel.getUsername())) ? "" : userQuery, false);
searchView.setQuery((cookieModel != null && userQuery != null && userQuery.equals("@" + cookieModel.getUsername())) ? "" : userQuery, false);
menu.findItem(R.id.action_about).setVisible(false); menu.findItem(R.id.action_about).setVisible(false);
menu.findItem(R.id.action_settings).setVisible(false); menu.findItem(R.id.action_settings).setVisible(false);
menu.findItem(R.id.action_dms).setVisible(false); menu.findItem(R.id.action_dms).setVisible(false);
@ -399,7 +402,10 @@ public final class Main extends BaseLanguageActivity {
private void cancelSuggestionsAsync() { private void cancelSuggestionsAsync() {
if (prevSuggestionAsync != null) if (prevSuggestionAsync != null)
try { prevSuggestionAsync.cancel(true); } catch (final Exception ignored) { }
try {
prevSuggestionAsync.cancel(true);
} catch (final Exception ignored) {
}
} }
@Override @Override
@ -410,7 +416,7 @@ public final class Main extends BaseLanguageActivity {
closeAnyOpenDrawer(); closeAnyOpenDrawer();
addToStack(); addToStack();
userQuery = (query.contains("@") || query.contains("#")) ? query : ("@"+query);
userQuery = (query.contains("@") || query.contains("#")) ? query : ("@" + query);
searchAction.collapseActionView(); searchAction.collapseActionView();
searchView.setIconified(true); searchView.setIconified(true);
searchView.setIconified(true); searchView.setIconified(true);
@ -467,8 +473,7 @@ public final class Main extends BaseLanguageActivity {
mainHelper.onRefresh(); mainHelper.onRefresh();
return; return;
} }
}
else {
} else {
finish(); finish();
} }
} }

54
app/src/main/java/awais/instagrabber/adapters/DirectMessageInboxAdapter.java

@ -0,0 +1,54 @@
package awais.instagrabber.adapters;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import awais.instagrabber.adapters.viewholder.DirectMessageInboxItemViewHolder;
import awais.instagrabber.databinding.LayoutIncludeSimpleItemBinding;
import awais.instagrabber.models.direct_messages.InboxThreadModel;
public final class DirectMessageInboxAdapter extends ListAdapter<InboxThreadModel, DirectMessageInboxItemViewHolder> {
private final OnItemClickListener onClickListener;
private static final DiffUtil.ItemCallback<InboxThreadModel> diffCallback = new DiffUtil.ItemCallback<InboxThreadModel>() {
@Override
public boolean areItemsTheSame(@NonNull final InboxThreadModel oldItem, @NonNull final InboxThreadModel newItem) {
return oldItem.getThreadId().equals(newItem.getThreadId());
}
@Override
public boolean areContentsTheSame(@NonNull final InboxThreadModel oldItem, @NonNull final InboxThreadModel newItem) {
return oldItem.equals(newItem);
}
};
public DirectMessageInboxAdapter(final OnItemClickListener onClickListener) {
super(diffCallback);
this.onClickListener = onClickListener;
}
@NonNull
@Override
public DirectMessageInboxItemViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int type) {
final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
final LayoutIncludeSimpleItemBinding binding = LayoutIncludeSimpleItemBinding.inflate(layoutInflater, parent, false);
return new DirectMessageInboxItemViewHolder(binding);
}
@Override
public void onBindViewHolder(@NonNull final DirectMessageInboxItemViewHolder holder, final int position) {
final InboxThreadModel threadModel = getItem(position);
if (onClickListener != null) {
holder.itemView.setOnClickListener((v) -> onClickListener.onItemClick(threadModel));
}
holder.bind(threadModel);
}
public interface OnItemClickListener {
void onItemClick(final InboxThreadModel inboxThreadModel);
}
}

131
app/src/main/java/awais/instagrabber/adapters/DirectMessagesAdapter.java

@ -1,131 +0,0 @@
package awais.instagrabber.adapters;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.core.text.HtmlCompat;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestManager;
import java.util.ArrayList;
import awais.instagrabber.R;
import awais.instagrabber.adapters.viewholder.DirectMessageViewHolder;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.direct_messages.DirectItemModel;
import awais.instagrabber.models.direct_messages.DirectItemModel.DirectItemActionLogModel;
import awais.instagrabber.models.direct_messages.DirectItemModel.DirectItemReelShareModel;
import awais.instagrabber.models.direct_messages.InboxThreadModel;
import awais.instagrabber.models.enums.DirectItemType;
public final class DirectMessagesAdapter extends RecyclerView.Adapter<DirectMessageViewHolder> {
private final ArrayList<InboxThreadModel> inboxThreadModels;
private final View.OnClickListener onClickListener;
private LayoutInflater layoutInflater;
public DirectMessagesAdapter(final ArrayList<InboxThreadModel> inboxThreadModels, final View.OnClickListener onClickListener) {
this.inboxThreadModels = inboxThreadModels;
this.onClickListener = onClickListener;
}
@NonNull
@Override
public DirectMessageViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int type) {
if (layoutInflater == null) layoutInflater = LayoutInflater.from(parent.getContext());
return new DirectMessageViewHolder(layoutInflater.inflate(R.layout.layout_include_simple_item, parent, false),
onClickListener);
}
@Override
public void onBindViewHolder(@NonNull final DirectMessageViewHolder holder, final int position) {
final InboxThreadModel threadModel = inboxThreadModels.get(position);
final DirectItemModel[] itemModels;
holder.itemView.setTag(threadModel);
final RequestManager glideRequestManager = Glide.with(holder.itemView);
if (threadModel != null && (itemModels = threadModel.getItems()) != null) {
final ProfileModel[] users = threadModel.getUsers();
if (users.length > 1) {
holder.ivProfilePic.setVisibility(View.GONE);
holder.multipleProfilePicsContainer.setVisibility(View.VISIBLE);
for (int i = 0; i < Math.min(3, users.length); ++i)
glideRequestManager.load(users[i].getSdProfilePic()).into(holder.multipleProfilePics[i]);
} else {
holder.ivProfilePic.setVisibility(View.VISIBLE);
holder.multipleProfilePicsContainer.setVisibility(View.GONE);
glideRequestManager.load(users.length == 1 ? users[0].getSdProfilePic() : null).into(holder.ivProfilePic);
}
holder.tvUsername.setText(threadModel.getThreadTitle());
final DirectItemModel lastItemModel = itemModels[itemModels.length - 1];
final DirectItemType itemType = lastItemModel.getItemType();
holder.notTextType.setVisibility(itemType != DirectItemType.TEXT ? View.VISIBLE : View.GONE);
final Context context = layoutInflater.getContext();
final CharSequence messageText;
switch (itemType) {
case TEXT:
case LIKE:
messageText = lastItemModel.getText();
break;
case LINK:
messageText = context.getString(R.string.direct_messages_sent_link);
break;
case MEDIA:
case MEDIA_SHARE:
messageText = context.getString(R.string.direct_messages_sent_media);
break;
case ACTION_LOG:
final DirectItemActionLogModel logModel = lastItemModel.getActionLogModel();
messageText = logModel != null ? logModel.getDescription() : "...";
break;
case REEL_SHARE:
final DirectItemReelShareModel reelShare = lastItemModel.getReelShare();
if (reelShare == null)
messageText = context.getString(R.string.direct_messages_sent_media);
else {
final String reelType = reelShare.getType();
final int textRes;
if ("reply".equals(reelType))
textRes = R.string.direct_messages_replied_story;
else if ("mention".equals(reelType))
textRes = R.string.direct_messages_mention_story;
else if ("reaction".equals(reelType))
textRes = R.string.direct_messages_reacted_story;
else textRes = R.string.direct_messages_sent_media;
messageText = context.getString(textRes) + " : " + reelShare.getText();
}
break;
case RAVEN_MEDIA:
messageText = context.getString(R.string.direct_messages_sent_media);
break;
default:
messageText = "<i>Unsupported message</i>";
}
holder.tvMessage.setText(HtmlCompat.fromHtml(messageText.toString(), 63));
holder.tvDate.setText(lastItemModel.getDateTime());
}
}
@Override
public int getItemCount() {
return inboxThreadModels == null ? 0 : inboxThreadModels.size();
}
}

400
app/src/main/java/awais/instagrabber/adapters/MessageItemsAdapter.java

@ -1,410 +1,66 @@
package awais.instagrabber.adapters; package awais.instagrabber.adapters;
import android.content.Context;
import android.content.res.Resources;
import android.text.Spanned;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.text.HtmlCompat;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestManager;
import java.util.List;
import java.util.ArrayList;
import awais.instagrabber.R;
import awais.instagrabber.activities.Main;
import awais.instagrabber.adapters.viewholder.directmessages.TextMessageViewHolder;
import awais.instagrabber.adapters.viewholder.directmessages.DirectMessageViewHolder;
import awais.instagrabber.databinding.ItemMessageItemBinding;
import awais.instagrabber.interfaces.MentionClickListener; import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.ProfileModel; import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.direct_messages.DirectItemModel; import awais.instagrabber.models.direct_messages.DirectItemModel;
import awais.instagrabber.models.direct_messages.DirectItemModel.DirectItemMediaModel;
import awais.instagrabber.models.direct_messages.DirectItemModel.DirectItemRavenMediaModel;
import awais.instagrabber.models.enums.DirectItemType;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.models.enums.RavenExpiringMediaType;
import awais.instagrabber.models.enums.RavenMediaViewType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.models.direct_messages.DirectItemModel.DirectItemLinkContext;
import static awais.instagrabber.models.direct_messages.DirectItemModel.DirectItemLinkModel;
import static awais.instagrabber.models.direct_messages.DirectItemModel.DirectItemReelShareModel;
import static awais.instagrabber.models.direct_messages.DirectItemModel.DirectItemVoiceMediaModel;
import static awais.instagrabber.models.direct_messages.DirectItemModel.RavenExpiringMediaActionSummaryModel;
public final class MessageItemsAdapter extends RecyclerView.Adapter<TextMessageViewHolder> {
private static final int MESSAGE_INCOMING = 69, MESSAGE_OUTGOING = 420;
private final ProfileModel myProfileHolder =
new ProfileModel(false, false, false,
Utils.getUserIdFromCookie(Utils.settingsHelper.getString(Constants.COOKIE)),
null, null, null, null, null, null, 0, 0, 0, false, false, false, false);
private final ArrayList<DirectItemModel> directItemModels;
private final ArrayList<ProfileModel> users, leftusers;
public final class MessageItemsAdapter extends ListAdapter<DirectItemModel, DirectMessageViewHolder> {
private final List<ProfileModel> users;
private final List<ProfileModel> leftUsers;
private final View.OnClickListener onClickListener; private final View.OnClickListener onClickListener;
private final MentionClickListener mentionClickListener; private final MentionClickListener mentionClickListener;
private final View.OnClickListener openProfileClickListener = v -> {
final Object tag = v.getTag();
if (tag instanceof ProfileModel) {
// todo do profile stuff
final ProfileModel profileModel = (ProfileModel) tag;
Log.d("AWAISKING_APP", "--> " + profileModel);
}
};
private final int itemMargin;
private DirectItemVoiceMediaModel prevVoiceModel;
private ImageView prevPlayIcon;
private final View.OnClickListener voicePlayClickListener = v -> {
final Object tag = v.getTag();
if (v instanceof ViewGroup && tag instanceof DirectItemVoiceMediaModel) {
final ImageView playIcon = (ImageView) ((ViewGroup) v).getChildAt(0);
final DirectItemVoiceMediaModel voiceMediaModel = (DirectItemVoiceMediaModel) tag;
final boolean voicePlaying = voiceMediaModel.isPlaying();
voiceMediaModel.setPlaying(!voicePlaying);
if (voiceMediaModel == prevVoiceModel) {
// todo pause / resume
} else {
// todo release prev audio, start new voice
if (prevVoiceModel != null) prevVoiceModel.setPlaying(false);
if (prevPlayIcon != null) prevPlayIcon.setImageResource(android.R.drawable.ic_media_play);
}
if (voicePlaying) {
playIcon.setImageResource(android.R.drawable.ic_media_play);
} else {
playIcon.setImageResource(android.R.drawable.ic_media_pause);
private static final DiffUtil.ItemCallback<DirectItemModel> diffCallback = new DiffUtil.ItemCallback<DirectItemModel>() {
@Override
public boolean areItemsTheSame(@NonNull final DirectItemModel oldItem, @NonNull final DirectItemModel newItem) {
return oldItem.getItemId().equals(newItem.getItemId());
} }
prevVoiceModel = voiceMediaModel;
prevPlayIcon = playIcon;
@Override
public boolean areContentsTheSame(@NonNull final DirectItemModel oldItem, @NonNull final DirectItemModel newItem) {
return oldItem.getItemId().equals(newItem.getItemId());
} }
}; };
private Context context;
private LayoutInflater layoutInflater;
private String strDmYou;
public MessageItemsAdapter(final ArrayList<DirectItemModel> directItemModels, final ArrayList<ProfileModel> users,
final ArrayList<ProfileModel> leftusers, final View.OnClickListener onClickListener,
public MessageItemsAdapter(final List<ProfileModel> users,
final List<ProfileModel> leftUsers,
final View.OnClickListener onClickListener,
final MentionClickListener mentionClickListener) { final MentionClickListener mentionClickListener) {
super(diffCallback);
this.users = users; this.users = users;
this.leftusers = leftusers;
this.directItemModels = directItemModels;
this.leftUsers = leftUsers;
this.onClickListener = onClickListener; this.onClickListener = onClickListener;
this.mentionClickListener = mentionClickListener; this.mentionClickListener = mentionClickListener;
this.itemMargin = Utils.displayMetrics.widthPixels / 5;
} }
@NonNull @NonNull
@Override @Override
public TextMessageViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int type) {
if (context == null) context = parent.getContext();
if (strDmYou == null) strDmYou = context.getString(R.string.direct_messages_you);
if (layoutInflater == null) layoutInflater = LayoutInflater.from(context);
return new TextMessageViewHolder(layoutInflater.inflate(R.layout.item_message_item, parent, false),
onClickListener, mentionClickListener);
public DirectMessageViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int type) {
final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
final ItemMessageItemBinding binding = ItemMessageItemBinding.inflate(layoutInflater, parent, false);
return new DirectMessageViewHolder(binding, users, leftUsers);
} }
@Override @Override
public void onBindViewHolder(@NonNull final TextMessageViewHolder holder, final int position) {
final DirectItemModel directItemModel = directItemModels.get(position);
holder.itemView.setTag(directItemModel);
if (directItemModel != null) {
final DirectItemType itemType = directItemModel.getItemType();
final ProfileModel user = getUser(directItemModel.getUserId());
final int type = user == myProfileHolder ? MESSAGE_OUTGOING : MESSAGE_INCOMING;
final RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) holder.itemView.getLayoutParams();
layoutParams.setMargins(type == MESSAGE_OUTGOING ? itemMargin : 0, 0,
type == MESSAGE_INCOMING ? itemMargin : 0, 0);
holder.tvMessage.setVisibility(View.GONE);
holder.voiceMessageContainer.setVisibility(View.GONE);
holder.ivAnimatedMessage.setVisibility(View.GONE);
holder.linkMessageContainer.setVisibility(View.GONE);
holder.mediaMessageContainer.setVisibility(View.GONE);
holder.mediaTypeIcon.setVisibility(View.GONE);
holder.mediaExpiredIcon.setVisibility(View.GONE);
holder.profileMessageContainer.setVisibility(View.GONE);
holder.isVerified.setVisibility(View.GONE);
holder.btnOpenProfile.setVisibility(View.GONE);
holder.btnOpenProfile.setOnClickListener(null);
holder.btnOpenProfile.setTag(null);
CharSequence text = "?";
if (user != null && user != myProfileHolder) text = user.getUsername();
else if (user == myProfileHolder) text = strDmYou;
text = text + " - " + directItemModel.getDateTime();
holder.tvUsername.setText(text);
holder.ivProfilePic.setVisibility(type == MESSAGE_INCOMING ? View.VISIBLE : View.GONE);
final RequestManager glideRequestManager = Glide.with(holder.itemView);
if (type == MESSAGE_INCOMING && user != null)
glideRequestManager.load(user.getSdProfilePic()).into(holder.ivProfilePic);
DirectItemMediaModel mediaModel = directItemModel.getMediaModel();
switch (itemType) {
case PLACEHOLDER:
holder.tvMessage.setText(HtmlCompat.fromHtml(directItemModel.getText().toString(), 63));
holder.tvMessage.setVisibility(View.VISIBLE);
break;
case TEXT:
case LIKE:
text = directItemModel.getText();
text = Utils.getSpannableUrl(text.toString()); // for urls
if (Utils.hasMentions(text)) text = Utils.getMentionText(text); // for mentions
if (text instanceof Spanned) holder.tvMessage.setText(text, TextView.BufferType.SPANNABLE);
else if (text == "") holder.tvMessage.setText(context.getText(R.string.dms_inbox_raven_message_unknown));
else holder.tvMessage.setText(text);
holder.tvMessage.setVisibility(View.VISIBLE);
break;
case LINK: {
final DirectItemLinkModel link = directItemModel.getLinkModel();
final DirectItemLinkContext linkContext = link.getLinkContext();
final String linkImageUrl = linkContext.getLinkImageUrl();
if (!Utils.isEmpty(linkImageUrl)) {
glideRequestManager.load(linkImageUrl).into(holder.ivLinkPreview);
holder.tvLinkTitle.setText(linkContext.getLinkTitle());
holder.tvLinkSummary.setText(linkContext.getLinkSummary());
holder.ivLinkPreview.setVisibility(View.VISIBLE);
holder.linkMessageContainer.setVisibility(View.VISIBLE);
}
holder.tvMessage.setText(Utils.getSpannableUrl(link.getText()));
holder.tvMessage.setVisibility(View.VISIBLE);
}
break;
case MEDIA_SHARE:
{
final ProfileModel modelUser = mediaModel.getUser();
if (modelUser != null) {
holder.tvMessage.setText(HtmlCompat.fromHtml("<small>"+context.getString(R.string.dms_inbox_media_shared_from, modelUser.getUsername())+"</small>", 63));
holder.tvMessage.setVisibility(View.VISIBLE);
}
}
case MEDIA: {
glideRequestManager.load(mediaModel.getThumbUrl()).into(holder.ivMediaPreview);
final MediaItemType modelMediaType = mediaModel.getMediaType();
holder.mediaTypeIcon.setVisibility(modelMediaType == MediaItemType.MEDIA_TYPE_VIDEO ||
modelMediaType == MediaItemType.MEDIA_TYPE_SLIDER ? View.VISIBLE : View.GONE);
holder.mediaMessageContainer.setVisibility(View.VISIBLE);
}
break;
case RAVEN_MEDIA: {
final DirectItemRavenMediaModel ravenMediaModel = directItemModel.getRavenMediaModel();
final boolean isExpired = ravenMediaModel == null || (mediaModel = ravenMediaModel.getMedia()) == null ||
Utils.isEmpty(mediaModel.getThumbUrl()) && mediaModel.getPk() < 1;
final RavenExpiringMediaActionSummaryModel mediaActionSummary = ravenMediaModel.getExpiringMediaActionSummary();
holder.mediaExpiredIcon.setVisibility(isExpired ? View.VISIBLE : View.GONE);
int textRes = R.string.dms_inbox_raven_media_unknown;
if (isExpired) textRes = R.string.dms_inbox_raven_media_expired;
if (!isExpired) {
if (mediaActionSummary != null) {
final RavenExpiringMediaType expiringMediaType = mediaActionSummary.getType();
if (expiringMediaType == RavenExpiringMediaType.RAVEN_DELIVERED)
textRes = R.string.dms_inbox_raven_media_delivered;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_SENT)
textRes = R.string.dms_inbox_raven_media_sent;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_OPENED)
textRes = R.string.dms_inbox_raven_media_opened;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_REPLAYED)
textRes = R.string.dms_inbox_raven_media_replayed;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_SENDING)
textRes = R.string.dms_inbox_raven_media_sending;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_BLOCKED)
textRes = R.string.dms_inbox_raven_media_blocked;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_SUGGESTED)
textRes = R.string.dms_inbox_raven_media_suggested;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_SCREENSHOT)
textRes = R.string.dms_inbox_raven_media_screenshot;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_CANNOT_DELIVER)
textRes = R.string.dms_inbox_raven_media_cant_deliver;
}
final RavenMediaViewType ravenMediaViewType = ravenMediaModel.getViewType();
if (ravenMediaViewType == RavenMediaViewType.PERMANENT || ravenMediaViewType == RavenMediaViewType.REPLAYABLE) {
final MediaItemType mediaType = mediaModel.getMediaType();
textRes = -1;
holder.mediaTypeIcon.setVisibility(mediaType == MediaItemType.MEDIA_TYPE_VIDEO ||
mediaType == MediaItemType.MEDIA_TYPE_SLIDER ? View.VISIBLE : View.GONE);
glideRequestManager.load(mediaModel.getThumbUrl()).into(holder.ivMediaPreview);
holder.mediaMessageContainer.setVisibility(View.VISIBLE);
}
}
if (textRes != -1) {
holder.tvMessage.setText(context.getText(textRes));
holder.tvMessage.setVisibility(View.VISIBLE);
}
}
break;
case REEL_SHARE: {
final DirectItemReelShareModel reelShare = directItemModel.getReelShare();
if (!Utils.isEmpty(text = reelShare.getText())) {
holder.tvMessage.setText(text);
holder.tvMessage.setVisibility(View.VISIBLE);
}
final DirectItemMediaModel reelShareMedia = reelShare.getMedia();
final MediaItemType mediaType = reelShareMedia.getMediaType();
if (mediaType == null)
holder.mediaExpiredIcon.setVisibility(View.VISIBLE);
else {
holder.mediaTypeIcon.setVisibility(mediaType == MediaItemType.MEDIA_TYPE_VIDEO ||
mediaType == MediaItemType.MEDIA_TYPE_SLIDER ? View.VISIBLE : View.GONE);
glideRequestManager.load(reelShareMedia.getThumbUrl()).into(holder.ivMediaPreview);
holder.mediaMessageContainer.setVisibility(View.VISIBLE);
}
}
break;
case STORY_SHARE: {
final DirectItemReelShareModel reelShare = directItemModel.getReelShare();
if (reelShare == null) {
holder.tvMessage.setText(HtmlCompat.fromHtml(directItemModel.getText().toString(), 63));
holder.tvMessage.setVisibility(View.VISIBLE);
}
else {
if (!Utils.isEmpty(text = reelShare.getText())) {
holder.tvMessage.setText(text);
holder.tvMessage.setVisibility(View.VISIBLE);
}
final DirectItemMediaModel reelShareMedia = reelShare.getMedia();
final MediaItemType mediaType = reelShareMedia.getMediaType();
holder.mediaTypeIcon.setVisibility(mediaType == MediaItemType.MEDIA_TYPE_VIDEO ||
mediaType == MediaItemType.MEDIA_TYPE_SLIDER ? View.VISIBLE : View.GONE);
glideRequestManager.load(reelShareMedia.getThumbUrl()).into(holder.ivMediaPreview);
holder.mediaMessageContainer.setVisibility(View.VISIBLE);
}
}
break;
case VOICE_MEDIA: {
final DirectItemVoiceMediaModel voiceMediaModel = directItemModel.getVoiceMediaModel();
if (voiceMediaModel != null) {
final int[] waveformData = voiceMediaModel.getWaveformData();
if (waveformData != null) holder.waveformSeekBar.setSample(waveformData);
final long durationMs = voiceMediaModel.getDurationMs();
holder.tvVoiceDuration.setText(Utils.millisToString(durationMs));
holder.waveformSeekBar.setProgress(voiceMediaModel.getProgress());
holder.waveformSeekBar.setProgressChangeListener((waveformSeekBar, progress, fromUser) -> {
// todo progress audio player
voiceMediaModel.setProgress(progress);
if (fromUser)
holder.tvVoiceDuration.setText(Utils.millisToString(durationMs * progress / 100));
});
holder.btnPlayVoice.setTag(voiceMediaModel);
holder.btnPlayVoice.setOnClickListener(voicePlayClickListener);
} else {
holder.waveformSeekBar.setProgress(0);
}
holder.voiceMessageContainer.setVisibility(View.VISIBLE);
}
break;
case ANIMATED_MEDIA: {
glideRequestManager.asGif().load(directItemModel.getAnimatedMediaModel().getGifUrl())
.into(holder.ivAnimatedMessage);
holder.ivAnimatedMessage.setVisibility(View.VISIBLE);
}
break;
case PROFILE: {
final ProfileModel profileModel = directItemModel.getProfileModel();
Glide.with(holder.ivMessageProfilePic).load(profileModel.getSdProfilePic())
.into(holder.ivMessageProfilePic);
holder.btnOpenProfile.setTag(profileModel);
holder.btnOpenProfile.setOnClickListener(openProfileClickListener);
holder.tvProfileName.setText(profileModel.getName());
holder.tvProfileUsername.setText(profileModel.getUsername());
holder.isVerified.setVisibility(profileModel.isVerified() ? View.VISIBLE : View.GONE);
holder.btnOpenProfile.setVisibility(View.VISIBLE);
holder.profileMessageContainer.setVisibility(View.VISIBLE);
}
break;
case VIDEO_CALL_EVENT: {
// todo add call event info
holder.tvMessage.setVisibility(View.VISIBLE);
holder.itemView.setBackgroundColor(0xFF_1F90E6);
}
break;
case ACTION_LOG: {
text = directItemModel.getActionLogModel().getDescription();
holder.tvMessage.setText(HtmlCompat.fromHtml("<small>"+text+"</small>", 63));
holder.tvMessage.setVisibility(View.VISIBLE);
}
break;
}
}
public void onBindViewHolder(@NonNull final DirectMessageViewHolder holder, final int position) {
final DirectItemModel directItemModel = getItem(position);
holder.bind(directItemModel);
} }
@Override @Override
public int getItemViewType(final int position) { public int getItemViewType(final int position) {
return directItemModels.get(position).getItemType().ordinal();
}
@Override
public int getItemCount() {
return directItemModels == null ? 0 : directItemModels.size();
}
@Nullable
private ProfileModel getUser(final long userId) {
if (users != null) {
ProfileModel result = myProfileHolder;
for (final ProfileModel user : users) {
if (Long.toString(userId).equals(user.getId())) result = user;
}
if (leftusers != null)
for (final ProfileModel leftuser : leftusers) {
if (Long.toString(userId).equals(leftuser.getId())) result = leftuser;
}
return result;
}
return null;
return getItem(position).getItemType().ordinal();
} }
} }

107
app/src/main/java/awais/instagrabber/adapters/viewholder/DirectMessageInboxItemViewHolder.java

@ -0,0 +1,107 @@
package awais.instagrabber.adapters.viewholder;
import android.content.Context;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.core.text.HtmlCompat;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestManager;
import awais.instagrabber.R;
import awais.instagrabber.databinding.LayoutIncludeSimpleItemBinding;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.direct_messages.DirectItemModel;
import awais.instagrabber.models.direct_messages.InboxThreadModel;
import awais.instagrabber.models.enums.DirectItemType;
public final class DirectMessageInboxItemViewHolder extends RecyclerView.ViewHolder {
private final LinearLayout multipleProfilePicsContainer;
private final ImageView[] multipleProfilePics;
private final LayoutIncludeSimpleItemBinding binding;
public DirectMessageInboxItemViewHolder(@NonNull final LayoutIncludeSimpleItemBinding binding) {
super(binding.getRoot());
this.binding = binding;
binding.tvLikes.setVisibility(View.GONE);
multipleProfilePicsContainer = binding.container;
final LinearLayout containerChild = (LinearLayout) multipleProfilePicsContainer.getChildAt(1);
multipleProfilePics = new ImageView[]{
(ImageView) multipleProfilePicsContainer.getChildAt(0),
(ImageView) containerChild.getChildAt(0),
(ImageView) containerChild.getChildAt(1)
};
binding.tvDate.setSelected(true);
binding.tvUsername.setSelected(true);
}
public void bind(final InboxThreadModel model) {
final DirectItemModel[] itemModels;
if (model == null || (itemModels = model.getItems()) == null) {
return;
}
itemView.setTag(model);
final RequestManager glideRequestManager = Glide.with(itemView);
final ProfileModel[] users = model.getUsers();
if (users.length > 1) {
binding.ivProfilePic.setVisibility(View.GONE);
multipleProfilePicsContainer.setVisibility(View.VISIBLE);
for (int i = 0; i < Math.min(3, users.length); ++i)
glideRequestManager.load(users[i].getSdProfilePic()).into(multipleProfilePics[i]);
} else {
binding.ivProfilePic.setVisibility(View.VISIBLE);
multipleProfilePicsContainer.setVisibility(View.GONE);
glideRequestManager.load(users.length == 1 ? users[0].getSdProfilePic() : null).into(binding.ivProfilePic);
}
binding.tvUsername.setText(model.getThreadTitle());
final DirectItemModel lastItemModel = itemModels[itemModels.length - 1];
final DirectItemType itemType = lastItemModel.getItemType();
binding.notTextType.setVisibility(itemType != DirectItemType.TEXT ? View.VISIBLE : View.GONE);
final Context context = itemView.getContext();
final CharSequence messageText;
switch (itemType) {
case TEXT:
case LIKE:
messageText = lastItemModel.getText();
break;
case LINK:
messageText = context.getString(R.string.direct_messages_sent_link);
break;
case MEDIA:
case MEDIA_SHARE:
case RAVEN_MEDIA:
messageText = context.getString(R.string.direct_messages_sent_media);
break;
case ACTION_LOG:
final DirectItemModel.DirectItemActionLogModel logModel = lastItemModel.getActionLogModel();
messageText = logModel != null ? logModel.getDescription() : "...";
break;
case REEL_SHARE:
final DirectItemModel.DirectItemReelShareModel reelShare = lastItemModel.getReelShare();
if (reelShare == null)
messageText = context.getString(R.string.direct_messages_sent_media);
else {
final String reelType = reelShare.getType();
final int textRes;
if ("reply".equals(reelType))
textRes = R.string.direct_messages_replied_story;
else if ("mention".equals(reelType))
textRes = R.string.direct_messages_mention_story;
else if ("reaction".equals(reelType))
textRes = R.string.direct_messages_reacted_story;
else textRes = R.string.direct_messages_sent_media;
messageText = context.getString(textRes) + " : " + reelShare.getText();
}
break;
default:
messageText = "<i>Unsupported message</i>";
}
binding.tvComment.setText(HtmlCompat.fromHtml(messageText.toString(), HtmlCompat.FROM_HTML_MODE_COMPACT));
binding.tvDate.setText(lastItemModel.getDateTime());
}
}

43
app/src/main/java/awais/instagrabber/adapters/viewholder/DirectMessageViewHolder.java

@ -1,43 +0,0 @@
package awais.instagrabber.adapters.viewholder;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import awais.instagrabber.R;
public final class DirectMessageViewHolder extends RecyclerView.ViewHolder {
public final LinearLayout multipleProfilePicsContainer;
public final ImageView[] multipleProfilePics;
public final ImageView ivProfilePic, notTextType;
public final TextView tvUsername, tvDate, tvMessage;
public DirectMessageViewHolder(@NonNull final View itemView, final View.OnClickListener clickListener) {
super(itemView);
if (clickListener != null) itemView.setOnClickListener(clickListener);
itemView.findViewById(R.id.tvLikes).setVisibility(View.GONE);
tvDate = itemView.findViewById(R.id.tvDate);
tvMessage = itemView.findViewById(R.id.tvComment);
tvUsername = itemView.findViewById(R.id.tvUsername);
notTextType = itemView.findViewById(R.id.notTextType);
ivProfilePic = itemView.findViewById(R.id.ivProfilePic);
multipleProfilePicsContainer = itemView.findViewById(R.id.container);
final LinearLayout containerChild = (LinearLayout) multipleProfilePicsContainer.getChildAt(1);
multipleProfilePics = new ImageView[]{
(ImageView) multipleProfilePicsContainer.getChildAt(0),
(ImageView) containerChild.getChildAt(0),
(ImageView) containerChild.getChildAt(1)
};
tvDate.setSelected(true);
tvUsername.setSelected(true);
}
}

383
app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectMessageViewHolder.java

@ -0,0 +1,383 @@
package awais.instagrabber.adapters.viewholder.directmessages;
import android.content.Context;
import android.text.Spanned;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.core.text.HtmlCompat;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestManager;
import java.util.List;
import awais.instagrabber.R;
import awais.instagrabber.databinding.ItemMessageItemBinding;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.direct_messages.DirectItemModel;
import awais.instagrabber.models.enums.DirectItemType;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.models.enums.RavenExpiringMediaType;
import awais.instagrabber.models.enums.RavenMediaViewType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import static androidx.core.text.HtmlCompat.FROM_HTML_MODE_COMPACT;
public final class DirectMessageViewHolder extends RecyclerView.ViewHolder {
private static final String TAG = "DirectMessageViewHolder";
private static final int MESSAGE_INCOMING = 69;
private static final int MESSAGE_OUTGOING = 420;
private final ProfileModel myProfileHolder = ProfileModel.getDefaultProfileModel(Utils.getUserIdFromCookie(Utils.settingsHelper.getString(Constants.COOKIE)));
private final ItemMessageItemBinding binding;
private final List<ProfileModel> users;
private final List<ProfileModel> leftUsers;
private final int itemMargin;
private final String strDmYou;
private DirectItemModel.DirectItemVoiceMediaModel prevVoiceModel;
private ImageView prevPlayIcon;
private final View.OnClickListener voicePlayClickListener = v -> {
final Object tag = v.getTag();
if (v instanceof ViewGroup && tag instanceof DirectItemModel.DirectItemVoiceMediaModel) {
final ImageView playIcon = (ImageView) ((ViewGroup) v).getChildAt(0);
final DirectItemModel.DirectItemVoiceMediaModel voiceMediaModel = (DirectItemModel.DirectItemVoiceMediaModel) tag;
final boolean voicePlaying = voiceMediaModel.isPlaying();
voiceMediaModel.setPlaying(!voicePlaying);
if (voiceMediaModel == prevVoiceModel) {
// todo pause / resume
} else {
// todo release prev audio, start new voice
if (prevVoiceModel != null) prevVoiceModel.setPlaying(false);
if (prevPlayIcon != null)
prevPlayIcon.setImageResource(android.R.drawable.ic_media_play);
}
if (voicePlaying) {
playIcon.setImageResource(android.R.drawable.ic_media_play);
} else {
playIcon.setImageResource(android.R.drawable.ic_media_pause);
}
prevVoiceModel = voiceMediaModel;
prevPlayIcon = playIcon;
}
};
private final View.OnClickListener openProfileClickListener = v -> {
final Object tag = v.getTag();
if (tag instanceof ProfileModel) {
// todo do profile stuff
final ProfileModel profileModel = (ProfileModel) tag;
Log.d(TAG, "--> " + profileModel);
}
};
public DirectMessageViewHolder(final ItemMessageItemBinding binding,
final List<ProfileModel> users,
final List<ProfileModel> leftUsers) {
super(binding.getRoot());
this.binding = binding;
this.users = users;
this.leftUsers = leftUsers;
this.itemMargin = Utils.displayMetrics.widthPixels / 5;
strDmYou = binding.getRoot().getContext().getString(R.string.direct_messages_you);
}
public void bind(final DirectItemModel directItemModel) {
if (directItemModel == null) {
return;
}
final Context context = itemView.getContext();
itemView.setTag(directItemModel);
final DirectItemType itemType = directItemModel.getItemType();
final ProfileModel user = getUser(directItemModel.getUserId());
final int type = user == myProfileHolder ? MESSAGE_OUTGOING : MESSAGE_INCOMING;
final RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) itemView.getLayoutParams();
layoutParams.setMargins(type == MESSAGE_OUTGOING ? itemMargin : 0, 0,
type == MESSAGE_INCOMING ? itemMargin : 0, 0);
binding.tvMessage.setVisibility(View.GONE);
final View voiceMessageContainer = (View) binding.waveformSeekBar.getParent();
final View linkMessageContainer = (View) binding.ivLinkPreview.getParent();
final View mediaMessageContainer = (View) binding.ivMediaPreview.getParent();
final View mediaTypeIcon = binding.typeIcon;
final View profileMessageContainer = (View) binding.profileInfo.getParent();
voiceMessageContainer.setVisibility(View.GONE);
binding.ivAnimatedMessage.setVisibility(View.GONE);
linkMessageContainer.setVisibility(View.GONE);
mediaMessageContainer.setVisibility(View.GONE);
mediaTypeIcon.setVisibility(View.GONE);
binding.mediaExpiredIcon.setVisibility(View.GONE);
profileMessageContainer.setVisibility(View.GONE);
binding.isVerified.setVisibility(View.GONE);
final FrameLayout btnOpenProfile = binding.btnInfo;
btnOpenProfile.setVisibility(View.GONE);
btnOpenProfile.setOnClickListener(null);
btnOpenProfile.setTag(null);
CharSequence text = "?";
if (user != null && user != myProfileHolder) text = user.getUsername();
else if (user == myProfileHolder) text = strDmYou;
text = text + " - " + directItemModel.getDateTime();
binding.tvUsername.setText(text);
binding.ivProfilePic.setVisibility(type == MESSAGE_INCOMING ? View.VISIBLE : View.GONE);
final RequestManager glideRequestManager = Glide.with(itemView);
if (type == MESSAGE_INCOMING && user != null)
glideRequestManager.load(user.getSdProfilePic()).into(binding.ivProfilePic);
DirectItemModel.DirectItemMediaModel mediaModel = directItemModel.getMediaModel();
switch (itemType) {
case PLACEHOLDER:
binding.tvMessage.setText(HtmlCompat.fromHtml(directItemModel.getText().toString(), FROM_HTML_MODE_COMPACT));
binding.tvMessage.setVisibility(View.VISIBLE);
break;
case TEXT:
case LIKE:
text = directItemModel.getText();
text = Utils.getSpannableUrl(text.toString()); // for urls
if (Utils.hasMentions(text)) text = Utils.getMentionText(text); // for mentions
if (text instanceof Spanned)
binding.tvMessage.setText(text, TextView.BufferType.SPANNABLE);
else if (text == "") {
binding.tvMessage.setText(context.getText(R.string.dms_inbox_raven_message_unknown));
} else binding.tvMessage.setText(text);
binding.tvMessage.setVisibility(View.VISIBLE);
break;
case LINK: {
final DirectItemModel.DirectItemLinkModel link = directItemModel.getLinkModel();
final DirectItemModel.DirectItemLinkContext linkContext = link.getLinkContext();
final String linkImageUrl = linkContext.getLinkImageUrl();
if (!Utils.isEmpty(linkImageUrl)) {
glideRequestManager.load(linkImageUrl).into(binding.ivLinkPreview);
binding.tvLinkTitle.setText(linkContext.getLinkTitle());
binding.tvLinkSummary.setText(linkContext.getLinkSummary());
binding.ivLinkPreview.setVisibility(View.VISIBLE);
linkMessageContainer.setVisibility(View.VISIBLE);
}
binding.tvMessage.setText(Utils.getSpannableUrl(link.getText()));
binding.tvMessage.setVisibility(View.VISIBLE);
}
break;
case MEDIA_SHARE: {
final ProfileModel modelUser = mediaModel.getUser();
if (modelUser != null) {
binding.tvMessage.setText(HtmlCompat.fromHtml("<small>" + context.getString(R.string.dms_inbox_media_shared_from, modelUser.getUsername()) + "</small>", FROM_HTML_MODE_COMPACT));
binding.tvMessage.setVisibility(View.VISIBLE);
}
}
case MEDIA: {
glideRequestManager.load(mediaModel.getThumbUrl()).into(binding.ivMediaPreview);
final MediaItemType modelMediaType = mediaModel.getMediaType();
mediaTypeIcon.setVisibility(modelMediaType == MediaItemType.MEDIA_TYPE_VIDEO ||
modelMediaType == MediaItemType.MEDIA_TYPE_SLIDER ? View.VISIBLE : View.GONE);
mediaMessageContainer.setVisibility(View.VISIBLE);
}
break;
case RAVEN_MEDIA: {
final DirectItemModel.DirectItemRavenMediaModel ravenMediaModel = directItemModel.getRavenMediaModel();
final boolean isExpired = ravenMediaModel == null || (mediaModel = ravenMediaModel.getMedia()) == null ||
Utils.isEmpty(mediaModel.getThumbUrl()) && mediaModel.getPk() < 1;
DirectItemModel.RavenExpiringMediaActionSummaryModel mediaActionSummary = null;
if (ravenMediaModel != null) {
mediaActionSummary = ravenMediaModel.getExpiringMediaActionSummary();
}
binding.mediaExpiredIcon.setVisibility(isExpired ? View.VISIBLE : View.GONE);
int textRes = R.string.dms_inbox_raven_media_unknown;
if (isExpired) textRes = R.string.dms_inbox_raven_media_expired;
if (!isExpired) {
if (mediaActionSummary != null) {
final RavenExpiringMediaType expiringMediaType = mediaActionSummary.getType();
if (expiringMediaType == RavenExpiringMediaType.RAVEN_DELIVERED)
textRes = R.string.dms_inbox_raven_media_delivered;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_SENT)
textRes = R.string.dms_inbox_raven_media_sent;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_OPENED)
textRes = R.string.dms_inbox_raven_media_opened;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_REPLAYED)
textRes = R.string.dms_inbox_raven_media_replayed;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_SENDING)
textRes = R.string.dms_inbox_raven_media_sending;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_BLOCKED)
textRes = R.string.dms_inbox_raven_media_blocked;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_SUGGESTED)
textRes = R.string.dms_inbox_raven_media_suggested;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_SCREENSHOT)
textRes = R.string.dms_inbox_raven_media_screenshot;
else if (expiringMediaType == RavenExpiringMediaType.RAVEN_CANNOT_DELIVER)
textRes = R.string.dms_inbox_raven_media_cant_deliver;
}
final RavenMediaViewType ravenMediaViewType = ravenMediaModel.getViewType();
if (ravenMediaViewType == RavenMediaViewType.PERMANENT || ravenMediaViewType == RavenMediaViewType.REPLAYABLE) {
final MediaItemType mediaType = mediaModel.getMediaType();
textRes = -1;
mediaTypeIcon.setVisibility(mediaType == MediaItemType.MEDIA_TYPE_VIDEO ||
mediaType == MediaItemType.MEDIA_TYPE_SLIDER ? View.VISIBLE : View.GONE);
glideRequestManager.load(mediaModel.getThumbUrl()).into(binding.ivMediaPreview);
mediaMessageContainer.setVisibility(View.VISIBLE);
}
}
if (textRes != -1) {
binding.tvMessage.setText(context.getText(textRes));
binding.tvMessage.setVisibility(View.VISIBLE);
}
}
break;
case REEL_SHARE: {
final DirectItemModel.DirectItemReelShareModel reelShare = directItemModel.getReelShare();
if (!Utils.isEmpty(text = reelShare.getText())) {
binding.tvMessage.setText(text);
binding.tvMessage.setVisibility(View.VISIBLE);
}
final DirectItemModel.DirectItemMediaModel reelShareMedia = reelShare.getMedia();
final MediaItemType mediaType = reelShareMedia.getMediaType();
if (mediaType == null)
binding.mediaExpiredIcon.setVisibility(View.VISIBLE);
else {
mediaTypeIcon.setVisibility(mediaType == MediaItemType.MEDIA_TYPE_VIDEO ||
mediaType == MediaItemType.MEDIA_TYPE_SLIDER ? View.VISIBLE : View.GONE);
glideRequestManager.load(reelShareMedia.getThumbUrl()).into(binding.ivMediaPreview);
mediaMessageContainer.setVisibility(View.VISIBLE);
}
}
break;
case STORY_SHARE: {
final DirectItemModel.DirectItemReelShareModel reelShare = directItemModel.getReelShare();
if (reelShare == null) {
binding.tvMessage.setText(HtmlCompat.fromHtml(directItemModel.getText().toString(), FROM_HTML_MODE_COMPACT));
binding.tvMessage.setVisibility(View.VISIBLE);
} else {
if (!Utils.isEmpty(text = reelShare.getText())) {
binding.tvMessage.setText(text);
binding.tvMessage.setVisibility(View.VISIBLE);
}
final DirectItemModel.DirectItemMediaModel reelShareMedia = reelShare.getMedia();
final MediaItemType mediaType = reelShareMedia.getMediaType();
mediaTypeIcon.setVisibility(mediaType == MediaItemType.MEDIA_TYPE_VIDEO ||
mediaType == MediaItemType.MEDIA_TYPE_SLIDER ? View.VISIBLE : View.GONE);
glideRequestManager.load(reelShareMedia.getThumbUrl()).into(binding.ivMediaPreview);
mediaMessageContainer.setVisibility(View.VISIBLE);
}
}
break;
case VOICE_MEDIA: {
final DirectItemModel.DirectItemVoiceMediaModel voiceMediaModel = directItemModel.getVoiceMediaModel();
if (voiceMediaModel != null) {
final int[] waveformData = voiceMediaModel.getWaveformData();
if (waveformData != null) binding.waveformSeekBar.setSample(waveformData);
final long durationMs = voiceMediaModel.getDurationMs();
binding.tvVoiceDuration.setText(Utils.millisToString(durationMs));
binding.waveformSeekBar.setProgress(voiceMediaModel.getProgress());
binding.waveformSeekBar.setProgressChangeListener((waveformSeekBar, progress, fromUser) -> {
// todo progress audio player
voiceMediaModel.setProgress(progress);
if (fromUser)
binding.tvVoiceDuration.setText(Utils.millisToString(durationMs * progress / 100));
});
binding.btnPlayVoice.setTag(voiceMediaModel);
binding.btnPlayVoice.setOnClickListener(voicePlayClickListener);
} else {
binding.waveformSeekBar.setProgress(0);
}
voiceMessageContainer.setVisibility(View.VISIBLE);
}
break;
case ANIMATED_MEDIA: {
glideRequestManager.asGif().load(directItemModel.getAnimatedMediaModel().getGifUrl())
.into(binding.ivAnimatedMessage);
binding.ivAnimatedMessage.setVisibility(View.VISIBLE);
}
break;
case PROFILE: {
final ProfileModel profileModel = directItemModel.getProfileModel();
Glide.with(binding.profileInfo).load(profileModel.getSdProfilePic())
.into(binding.profileInfo);
btnOpenProfile.setTag(profileModel);
btnOpenProfile.setOnClickListener(openProfileClickListener);
binding.tvFullName.setText(profileModel.getName());
binding.profileInfoText.setText(profileModel.getUsername());
binding.isVerified.setVisibility(profileModel.isVerified() ? View.VISIBLE : View.GONE);
btnOpenProfile.setVisibility(View.VISIBLE);
profileMessageContainer.setVisibility(View.VISIBLE);
}
break;
case VIDEO_CALL_EVENT: {
// todo add call event info
binding.tvMessage.setVisibility(View.VISIBLE);
binding.profileInfoText.setBackgroundColor(0xFF_1F90E6);
}
break;
case ACTION_LOG: {
text = directItemModel.getActionLogModel().getDescription();
binding.tvMessage.setText(HtmlCompat.fromHtml("<small>" + text + "</small>", FROM_HTML_MODE_COMPACT));
binding.tvMessage.setVisibility(View.VISIBLE);
}
break;
}
}
@Nullable
private ProfileModel getUser(final long userId) {
if (users != null) {
ProfileModel result = myProfileHolder;
for (final ProfileModel user : users) {
if (Long.toString(userId).equals(user.getId())) result = user;
}
if (leftUsers != null)
for (final ProfileModel leftUser : leftUsers) {
if (Long.toString(userId).equals(leftUser.getId())) result = leftUser;
}
return result;
}
return null;
}
}

91
app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/TextMessageViewHolder.java

@ -1,91 +0,0 @@
package awais.instagrabber.adapters.viewholder.directmessages;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.cardview.widget.CardView;
import androidx.recyclerview.widget.RecyclerView;
import awais.instagrabber.R;
import awais.instagrabber.customviews.RamboTextView;
import awais.instagrabber.customviews.masoudss_waveform.WaveformSeekBar;
import awais.instagrabber.interfaces.MentionClickListener;
public final class TextMessageViewHolder extends RecyclerView.ViewHolder {
public final CardView rootCardView;
public final TextView tvUsername;
public final ImageView ivProfilePic;
// text message
public final RamboTextView tvMessage;
// expired message icon
public final View mediaExpiredIcon;
// media message
public final View mediaMessageContainer;
public final ImageView ivMediaPreview, mediaTypeIcon;
// profile messag
public final View profileMessageContainer, isVerified, btnOpenProfile;
public final TextView tvProfileUsername, tvProfileName;
public final ImageView ivMessageProfilePic;
// animated message
public final ImageView ivAnimatedMessage;
// link message
public final View linkMessageContainer;
public final ImageView ivLinkPreview;
public final TextView tvLinkTitle, tvLinkSummary;
// voice message
public final View voiceMessageContainer, btnPlayVoice;
public final WaveformSeekBar waveformSeekBar;
public final TextView tvVoiceDuration;
public TextMessageViewHolder(@NonNull final View itemView, final View.OnClickListener clickListener,
final MentionClickListener mentionClickListener) {
super(itemView);
if (clickListener != null) itemView.setOnClickListener(clickListener);
tvUsername = itemView.findViewById(R.id.tvUsername);
ivProfilePic = itemView.findViewById(R.id.ivProfilePic);
// text message
tvMessage = itemView.findViewById(R.id.tvMessage);
tvMessage.setCaptionIsExpandable(true);
tvMessage.setCaptionIsExpanded(true);
if (mentionClickListener != null) tvMessage.setMentionClickListener(mentionClickListener);
// root view
rootCardView = (CardView) tvMessage.getParent().getParent();
// expired message icon
mediaExpiredIcon = itemView.findViewById(R.id.mediaExpiredIcon);
// media message
ivMediaPreview = itemView.findViewById(R.id.ivMediaPreview);
mediaMessageContainer = (View) ivMediaPreview.getParent();
mediaTypeIcon = mediaMessageContainer.findViewById(R.id.typeIcon);
// profile message
btnOpenProfile = itemView.findViewById(R.id.btnInfo);
ivMessageProfilePic = itemView.findViewById(R.id.profileInfo);
profileMessageContainer = (View) ivMessageProfilePic.getParent();
isVerified = profileMessageContainer.findViewById(R.id.isVerified);
tvProfileName = profileMessageContainer.findViewById(R.id.tvFullName);
tvProfileUsername = profileMessageContainer.findViewById(R.id.profileInfoText);
// animated message
ivAnimatedMessage = itemView.findViewById(R.id.ivAnimatedMessage);
// link message
ivLinkPreview = itemView.findViewById(R.id.ivLinkPreview);
linkMessageContainer = (View) ivLinkPreview.getParent();
tvLinkTitle = linkMessageContainer.findViewById(R.id.tvLinkTitle);
tvLinkSummary = linkMessageContainer.findViewById(R.id.tvLinkSummary);
// voice message
waveformSeekBar = itemView.findViewById(R.id.waveformSeekBar);
voiceMessageContainer = (View) waveformSeekBar.getParent();
btnPlayVoice = voiceMessageContainer.findViewById(R.id.btnPlayVoice);
tvVoiceDuration = voiceMessageContainer.findViewById(R.id.tvVoiceDuration);
}
}

176
app/src/main/java/awais/instagrabber/asyncs/ImageUploader.java

@ -0,0 +1,176 @@
package awais.instagrabber.asyncs;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.util.Log;
import org.json.JSONObject;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.UUID;
import awais.instagrabber.models.ImageUploadOptions;
import awais.instagrabber.utils.Utils;
public class ImageUploader extends AsyncTask<ImageUploadOptions, Void, ImageUploader.ImageUploadResponse> {
private static final String TAG = "ImageUploader";
private static final long LOWER = 1000000000L;
private static final long UPPER = 9999999999L;
private OnImageUploadCompleteListener listener;
protected ImageUploadResponse doInBackground(final ImageUploadOptions... imageUploadOptions) {
if (imageUploadOptions == null || imageUploadOptions.length == 0 || imageUploadOptions[0] == null) {
return null;
}
HttpURLConnection connection = null;
OutputStream out = null;
InputStream inputStream = null;
BufferedReader r = null;
ByteArrayOutputStream baos = null;
try {
final ImageUploadOptions options = imageUploadOptions[0];
final Bitmap bitmap = options.getBitmap();
baos = new ByteArrayOutputStream();
final boolean compressResult = bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
if (!compressResult) {
Log.e(TAG, "Compress result was false!");
return null;
}
final byte[] bytes = baos.toByteArray();
final String contentLength = String.valueOf(bytes.length);
final Map<String, String> headers = new HashMap<>();
final String uploadId = String.valueOf(new Date().getTime());
final long random = LOWER + new Random().nextLong() * (UPPER - LOWER + 1);
final String name = String.format("%s_0_%s", uploadId, random);
final String waterfallId = options.getWaterfallId() != null ? options.getWaterfallId() : UUID.randomUUID().toString();
headers.put("X-Entity-Type", "image/jpeg");
headers.put("Offset", "0");
headers.put("X_FB_PHOTO_WATERFALL_ID", waterfallId);
headers.put("X-Instagram-Rupload-Params", new JSONObject(createPhotoRuploadParams(options, uploadId)).toString());
headers.put("X-Entity-Name", name);
headers.put("X-Entity-Length", contentLength);
headers.put("Content-Type", "application/octet-stream");
headers.put("Content-Length", contentLength);
headers.put("Accept-Encoding", "gzip");
final String url = "https://www.instagram.com/rupload_igphoto/" + name + "/";
connection = (HttpURLConnection) new URL(url).openConnection();
connection.setRequestMethod("POST");
connection.setUseCaches(false);
connection.setDoOutput(true);
Utils.setConnectionHeaders(connection, headers);
out = new BufferedOutputStream(connection.getOutputStream());
out.write(bytes);
out.flush();
final int responseCode = connection.getResponseCode();
Log.d(TAG, "response: " + responseCode);
final String responseCodeString = String.valueOf(responseCode);
final InputStream responseInputStream = responseCodeString.startsWith("4") || responseCodeString.startsWith("5")
? connection.getErrorStream() : connection.getInputStream();
r = new BufferedReader(new InputStreamReader(responseInputStream));
final StringBuilder builder = new StringBuilder();
for (String line = r.readLine(); line != null; line = r.readLine()) {
if (builder.length() != 0) {
builder.append("\n");
}
builder.append(line);
}
return new ImageUploadResponse(responseCode, new JSONObject(builder.toString()));
} catch (Exception ex) {
Log.e(TAG, "Image upload error:", ex);
} finally {
if (r != null) {
try {
r.close();
} catch (IOException ignored) {}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException ignored) {}
}
if (out != null) {
try {
out.close();
} catch (IOException ignored) {}
}
if (connection != null) {
connection.disconnect();
}
if (baos != null) {
try {
baos.close();
} catch (IOException ignored) {}
}
}
return null;
}
@Override
protected void onPostExecute(final ImageUploadResponse response) {
if (listener != null) {
listener.onImageUploadComplete(response);
}
}
private Map<String, String> createPhotoRuploadParams(final ImageUploadOptions options, final String uploadId) {
final Map<String, Integer> retryContext = new HashMap<>();
retryContext.put("num_step_auto_retry", 0);
retryContext.put("num_reupload", 0);
retryContext.put("num_step_manual_retry", 0);
final String retryContextString = new JSONObject(retryContext).toString();
final Map<String, String> params = new HashMap<>();
params.put("retry_context", retryContextString);
params.put("media_type", "1");
params.put("upload_id", uploadId);
params.put("xsharing_user_ids", "[]");
final Map<String, String> imageCompression = new HashMap<>();
imageCompression.put("lib_name", "moz");
imageCompression.put("lib_version", "3.1.m");
imageCompression.put("quality", "80");
params.put("image_compression", new JSONObject(imageCompression).toString());
if (options.isSidecar()) {
params.put("is_sidecar", "1");
}
return params;
}
public void setOnTaskCompleteListener(final OnImageUploadCompleteListener listener) {
if (listener != null) {
this.listener = listener;
}
}
public interface OnImageUploadCompleteListener {
void onImageUploadComplete(ImageUploadResponse response);
}
public static class ImageUploadResponse {
private int responseCode;
private JSONObject response;
public ImageUploadResponse(int responseCode, JSONObject response) {
this.responseCode = responseCode;
this.response = response;
}
public int getResponseCode() {
return responseCode;
}
public JSONObject getResponse() {
return response;
}
}
}

32
app/src/main/java/awais/instagrabber/asyncs/direct_messages/UserInboxFetcher.java → app/src/main/java/awais/instagrabber/asyncs/direct_messages/DirectMessageInboxThreadFetcher.java

@ -9,6 +9,8 @@ import org.json.JSONObject;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import awais.instagrabber.BuildConfig; import awais.instagrabber.BuildConfig;
import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.interfaces.FetchListener;
@ -21,17 +23,21 @@ import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Utils.logCollector; import static awais.instagrabber.utils.Utils.logCollector;
import static awaisomereport.LogCollector.LogFile; import static awaisomereport.LogCollector.LogFile;
public final class UserInboxFetcher extends AsyncTask<Void, Void, InboxThreadModel> {
public final class DirectMessageInboxThreadFetcher extends AsyncTask<Void, Void, InboxThreadModel> {
private static final String TAG = "DMInboxThreadFetcher";
private final String id; private final String id;
private final String endCursor; private final String endCursor;
private final FetchListener<InboxThreadModel> fetchListener; private final FetchListener<InboxThreadModel> fetchListener;
private final String direction;
private final UserInboxDirection direction;
public UserInboxFetcher(final String id, final UserInboxDirection direction, final String endCursor,
public DirectMessageInboxThreadFetcher(final String id,
final UserInboxDirection direction,
final String cursor,
final FetchListener<InboxThreadModel> fetchListener) { final FetchListener<InboxThreadModel> fetchListener) {
this.id = id; this.id = id;
this.direction = "&direction=" + (direction == UserInboxDirection.NEWER ? "newer" : "older");
this.endCursor = !Utils.isEmpty(endCursor) ? "&cursor=" + endCursor : "";
this.direction = direction;
this.endCursor = cursor;
this.fetchListener = fetchListener; this.fetchListener = fetchListener;
} }
@ -39,11 +45,14 @@ public final class UserInboxFetcher extends AsyncTask<Void, Void, InboxThreadMod
@Override @Override
protected InboxThreadModel doInBackground(final Void... voids) { protected InboxThreadModel doInBackground(final Void... voids) {
InboxThreadModel result = null; InboxThreadModel result = null;
final String url = "https://i.instagram.com/api/v1/direct_v2/threads/" + id + "/?visual_message_return_type=unseen"
+ direction + endCursor;
// todo probably
// & seq_id = seqId
final Map<String, String> queryParamsMap = new HashMap<>();
queryParamsMap.put("visual_message_return_type", "unseen");
queryParamsMap.put("direction", direction.getValue());
if (!Utils.isEmpty(endCursor)) {
queryParamsMap.put("cursor", endCursor);
}
final String queryString = Utils.getQueryString(queryParamsMap);
final String url = "https://i.instagram.com/api/v1/direct_v2/threads/" + id + "/?" + queryString;
try { try {
final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection();
conn.setRequestProperty("User-Agent", Constants.I_USER_AGENT); conn.setRequestProperty("User-Agent", Constants.I_USER_AGENT);
@ -60,9 +69,8 @@ public final class UserInboxFetcher extends AsyncTask<Void, Void, InboxThreadMod
result = null; result = null;
if (logCollector != null) if (logCollector != null)
logCollector.appendException(e, LogFile.ASYNC_DMS_THREAD, "doInBackground"); logCollector.appendException(e, LogFile.ASYNC_DMS_THREAD, "doInBackground");
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
} }
return result; return result;
} }

207
app/src/main/java/awais/instagrabber/asyncs/direct_messages/DirectThreadBroadcaster.java

@ -0,0 +1,207 @@
package awais.instagrabber.asyncs.direct_messages;
import android.os.AsyncTask;
import android.util.Log;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class DirectThreadBroadcaster extends AsyncTask<DirectThreadBroadcaster.BroadcastOptions, Void, DirectThreadBroadcaster.DirectThreadBroadcastResponse> {
private static final String TAG = "DirectThreadBroadcaster";
private final String threadId;
private OnBroadcastCompleteListener listener;
public DirectThreadBroadcaster(String threadId) {
this.threadId = threadId;
}
@Override
protected DirectThreadBroadcastResponse doInBackground(final BroadcastOptions... broadcastOptionsArray) {
if (broadcastOptionsArray == null || broadcastOptionsArray.length == 0 || broadcastOptionsArray[0] == null) {
return null;
}
final BroadcastOptions broadcastOptions = broadcastOptionsArray[0];
final String cookie = settingsHelper.getString(Constants.COOKIE);
final String cc = UUID.randomUUID().toString();
final Map<String, String> form = new HashMap<>();
form.put("_csrftoken", cookie.split("csrftoken=")[1].split(";")[0]);
form.put("_uid", Utils.getUserIdFromCookie(cookie));
form.put("__uuid", settingsHelper.getString(Constants.DEVICE_UUID));
form.put("client_context", cc);
form.put("mutation_token", cc);
form.putAll(broadcastOptions.getFormMap());
form.put("thread_ids", String.format("[%s]", threadId));
form.put("action", "send_item");
final String message = new JSONObject(form).toString();
final String content = Utils.sign(message);
final String url = "https://i.instagram.com/api/v1/direct_v2/threads/broadcast/" + broadcastOptions.getItemType().getValue() + "/";
HttpURLConnection connection = null;
DataOutputStream outputStream = null;
BufferedReader r = null;
try {
connection = (HttpURLConnection) new URL(url).openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("User-Agent", Constants.I_USER_AGENT);
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
if (content != null) {
connection.setRequestProperty("Content-Length", "" + content.getBytes().length);
}
connection.setUseCaches(false);
connection.setDoOutput(true);
outputStream = new DataOutputStream(connection.getOutputStream());
outputStream.writeBytes(content);
outputStream.flush();
final int responseCode = connection.getResponseCode();
if (responseCode != HttpURLConnection.HTTP_OK) {
Log.d(TAG, responseCode + ": " + content + ": " + cookie);
return new DirectThreadBroadcastResponse(responseCode, null);
}
r = new BufferedReader(new InputStreamReader(connection.getInputStream()));
final StringBuilder builder = new StringBuilder();
for (String line = r.readLine(); line != null; line = r.readLine()) {
if (builder.length() != 0) {
builder.append("\n");
}
builder.append(line);
}
return new DirectThreadBroadcastResponse(responseCode, new JSONObject(builder.toString()));
} catch (Exception e) {
Log.e(TAG, "Error", e);
} finally {
if (r != null) {
try {
r.close();
} catch (IOException ignored) {
}
}
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException ignored) {
}
}
if (connection != null) {
connection.disconnect();
}
}
return null;
}
@Override
protected void onPostExecute(final DirectThreadBroadcastResponse result) {
if (listener != null) {
listener.onTaskComplete(result);
}
}
public void setOnTaskCompleteListener(final OnBroadcastCompleteListener listener) {
if (listener != null) {
this.listener = listener;
}
}
public interface OnBroadcastCompleteListener {
void onTaskComplete(DirectThreadBroadcastResponse response);
}
public enum ItemType {
TEXT("text"),
IMAGE("configure_photo");
private final String value;
ItemType(final String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
public static abstract class BroadcastOptions {
private final ItemType itemType;
public BroadcastOptions(final ItemType itemType) {
this.itemType = itemType;
}
public ItemType getItemType() {
return itemType;
}
abstract Map<String, String> getFormMap();
}
public static class TextBroadcastOptions extends BroadcastOptions {
private final String text;
public TextBroadcastOptions(String text) throws UnsupportedEncodingException {
super(ItemType.TEXT);
this.text = URLEncoder.encode(text, "UTF-8")
.replaceAll("\\+", "%20").replaceAll("%21", "!").replaceAll("%27", "'")
.replaceAll("%28", "(").replaceAll("%29", ")").replaceAll("%7E", "~");
}
@Override
Map<String, String> getFormMap() {
return Collections.singletonMap("text", text);
}
}
public static class ImageBroadcastOptions extends BroadcastOptions {
final boolean allowFullAspectRatio;
final String uploadId;
public ImageBroadcastOptions(final boolean allowFullAspectRatio, final String uploadId) {
super(ItemType.IMAGE);
this.allowFullAspectRatio = allowFullAspectRatio;
this.uploadId = uploadId;
}
@Override
Map<String, String> getFormMap() {
final Map<String, String> form = new HashMap<>();
form.put("allow_full_aspect_ratio", String.valueOf(allowFullAspectRatio));
form.put("upload_id", uploadId);
return form;
}
}
public static class DirectThreadBroadcastResponse {
private int responseCode;
private JSONObject response;
public DirectThreadBroadcastResponse(int responseCode, JSONObject response) {
this.responseCode = responseCode;
this.response = response;
}
public int getResponseCode() {
return responseCode;
}
public JSONObject getResponse() {
return response;
}
}
}

149
app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageInboxFragment.java

@ -0,0 +1,149 @@
package awais.instagrabber.fragments.directmessages;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.adapters.DirectMessageInboxAdapter;
import awais.instagrabber.asyncs.direct_messages.InboxFetcher;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.databinding.FragmentDirectMessagesInboxBinding;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.direct_messages.InboxModel;
import awais.instagrabber.models.direct_messages.InboxThreadModel;
import awais.instagrabber.utils.Utils;
public class DirectMessageInboxFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = "DirectMessagesInboxFrag";
private FragmentActivity fragmentActivity;
private SwipeRefreshLayout root;
private RecyclerView inboxList;
private RecyclerLazyLoader lazyLoader;
private LinearLayoutManager layoutManager;
private String endCursor;
private AsyncTask<Void, Void, InboxModel> currentlyRunning;
private InboxThreadModelListViewModel listViewModel;
private final FetchListener<InboxModel> fetchListener = new FetchListener<InboxModel>() {
@Override
public void doBefore() {
root.setRefreshing(true);
}
@Override
public void onResult(final InboxModel inboxModel) {
if (inboxModel != null) {
endCursor = inboxModel.getOldestCursor();
if ("MINCURSOR".equals(endCursor) || "MAXCURSOR".equals(endCursor))
endCursor = null;
// todo get request / unseen count from inboxModel
final InboxThreadModel[] threads = inboxModel.getThreads();
if (threads != null && threads.length > 0) {
List<InboxThreadModel> list = listViewModel.getList().getValue();
list = list != null ? new LinkedList<>(list) : new LinkedList<>();
// final int oldSize = list != null ? list.size() : 0;
final List<InboxThreadModel> newList = Arrays.asList(threads);
list.addAll(newList);
listViewModel.getList().postValue(list);
}
}
root.setRefreshing(false);
stopCurrentExecutor();
}
};
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fragmentActivity = requireActivity();
}
@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
final ViewGroup container,
final Bundle savedInstanceState) {
if (root != null) {
return root;
}
final FragmentDirectMessagesInboxBinding binding = FragmentDirectMessagesInboxBinding.inflate(inflater, container, false);
root = binding.getRoot();
root.setOnRefreshListener(this);
inboxList = binding.inboxList;
inboxList.setHasFixedSize(true);
layoutManager = new LinearLayoutManager(requireContext());
inboxList.setLayoutManager(layoutManager);
final DirectMessageInboxAdapter inboxAdapter = new DirectMessageInboxAdapter(inboxThreadModel -> {
final NavDirections action = DirectMessageInboxFragmentDirections.actionDMInboxFragmentToDMThreadFragment(inboxThreadModel.getThreadId(), inboxThreadModel.getThreadTitle());
NavHostFragment.findNavController(this).navigate(action);
});
inboxList.setAdapter(inboxAdapter);
listViewModel = new ViewModelProvider(fragmentActivity).get(InboxThreadModelListViewModel.class);
listViewModel.getList().observe(fragmentActivity, inboxAdapter::submitList);
initData();
return root;
}
@Override
public void onRefresh() {
endCursor = null;
lazyLoader.resetState();
listViewModel.getList().postValue(Collections.emptyList());
stopCurrentExecutor();
currentlyRunning = new InboxFetcher(null, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void initData() {
lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
if (!Utils.isEmpty(endCursor))
currentlyRunning = new InboxFetcher(endCursor, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
endCursor = null;
});
inboxList.addOnScrollListener(lazyLoader);
stopCurrentExecutor();
currentlyRunning = new InboxFetcher(null, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private void stopCurrentExecutor() {
if (currentlyRunning != null) {
try {
currentlyRunning.cancel(true);
} catch (final Exception e) {
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
}
}
}
public static class InboxThreadModelListViewModel extends ViewModel {
private MutableLiveData<List<InboxThreadModel>> list;
public MutableLiveData<List<InboxThreadModel>> getList() {
if (list == null) {
list = new MutableLiveData<>();
}
return list;
}
}
}

351
app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java

@ -0,0 +1,351 @@
package awais.instagrabber.fragments.directmessages;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import awais.instagrabber.R;
import awais.instagrabber.activities.PostViewer;
import awais.instagrabber.activities.ProfileViewer;
import awais.instagrabber.activities.StoryViewer;
import awais.instagrabber.adapters.MessageItemsAdapter;
import awais.instagrabber.asyncs.ImageUploader;
import awais.instagrabber.asyncs.direct_messages.DirectMessageInboxThreadFetcher;
import awais.instagrabber.asyncs.direct_messages.DirectThreadBroadcaster;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.databinding.FragmentDirectMessagesThreadBinding;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.ImageUploadOptions;
import awais.instagrabber.models.PostModel;
import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.direct_messages.DirectItemModel;
import awais.instagrabber.models.direct_messages.InboxThreadModel;
import awais.instagrabber.models.enums.DirectItemType;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.UserInboxDirection;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.Utils;
public class DirectMessageThreadFragment extends Fragment {
private static final String TAG = "DirectMessagesThreadFmt";
private static final int PICK_IMAGE = 100;
private FragmentActivity fragmentActivity;
private String threadId;
private String cursor;
private FragmentDirectMessagesThreadBinding binding;
private DirectItemModelListViewModel listViewModel;
private RecyclerView messageList;
private boolean hasSentSomething;
private boolean hasOlder = true;
private final ProfileModel myProfileHolder = ProfileModel.getDefaultProfileModel();
private final List<ProfileModel> users = new ArrayList<>();
private final List<ProfileModel> leftUsers = new ArrayList<>();
private final View.OnClickListener clickListener = v -> {
if (v == binding.commentSend) {
final String text = binding.commentText.getText().toString();
if (Utils.isEmpty(text)) {
Toast.makeText(requireContext(), R.string.comment_send_empty_comment, Toast.LENGTH_SHORT).show();
return;
}
sendText(text);
return;
}
if (v == binding.image) {
final Intent intent = new Intent();
intent.setType("image/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(Intent.createChooser(intent, getString(R.string.select_picture)), PICK_IMAGE);
}
};
private final FetchListener<InboxThreadModel> fetchListener = new FetchListener<InboxThreadModel>() {
@Override
public void doBefore() {
binding.swipeRefreshLayout.setRefreshing(true);
}
@Override
public void onResult(final InboxThreadModel result) {
if (result == null && ("MINCURSOR".equals(cursor) || "MAXCURSOR".equals(cursor) || Utils.isEmpty(cursor)))
Toast.makeText(requireContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
if (result != null) {
cursor = result.getOldestCursor();
hasOlder = result.hasOlder();
if ("MINCURSOR".equals(cursor) || "MAXCURSOR".equals(cursor)) {
cursor = null;
}
users.clear();
users.addAll(Arrays.asList(result.getUsers()));
leftUsers.clear();
leftUsers.addAll(Arrays.asList(result.getLeftUsers()));
// thread title is already comma separated username, so no need to set by ourselves
// String[] users = new String[result.getUsers().length];
// for (int i = 0; i < users.length; ++i) {
// users[i] = result.getUsers()[i].getUsername();
// }
List<DirectItemModel> list = listViewModel.getList().getValue();
final List<DirectItemModel> newList = Arrays.asList(result.getItems());
list = list != null ? new LinkedList<>(list) : new LinkedList<>();
if (hasSentSomething) {
list = newList;
hasSentSomething = false;
final Handler handler = new Handler();
handler.postDelayed(() -> {
if (messageList != null) {
messageList.smoothScrollToPosition(0);
}
}, 200);
} else {
list.addAll(newList);
}
listViewModel.getList().postValue(list);
}
binding.swipeRefreshLayout.setRefreshing(false);
}
};
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fragmentActivity = requireActivity();
}
@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
final ViewGroup container,
final Bundle savedInstanceState) {
binding = FragmentDirectMessagesThreadBinding.inflate(inflater, container, false);
final LinearLayout root = binding.getRoot();
if (getArguments() == null) {
return root;
}
threadId = DirectMessageThreadFragmentArgs.fromBundle(getArguments()).getThreadId();
binding.swipeRefreshLayout.setEnabled(false);
messageList = binding.messageList;
messageList.setHasFixedSize(true);
binding.commentSend.setOnClickListener(clickListener);
binding.image.setOnClickListener(clickListener);
final LinearLayoutManager layoutManager = new LinearLayoutManager(requireContext());
layoutManager.setReverseLayout(true);
// layoutManager.setStackFromEnd(true);
messageList.setLayoutManager(layoutManager);
messageList.addOnScrollListener(new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> {
if (Utils.isEmpty(cursor) || !hasOlder) {
return;
}
new DirectMessageInboxThreadFetcher(threadId, UserInboxDirection.OLDER, cursor, fetchListener).execute(); // serial because we don't want messages to be randomly ordered
}));
final View.OnClickListener onClickListener = v -> {
Object tag = v.getTag();
if (tag instanceof DirectItemModel) {
final DirectItemModel directItemModel = (DirectItemModel) tag;
final DirectItemType itemType = directItemModel.getItemType();
switch (itemType) {
case MEDIA_SHARE:
startActivity(new Intent(requireContext(), PostViewer.class)
.putExtra(Constants.EXTRAS_POST, new PostModel(directItemModel.getMediaModel().getCode(), false)));
break;
case LINK:
Intent linkIntent = new Intent(Intent.ACTION_VIEW);
linkIntent.setData(Uri.parse(directItemModel.getLinkModel().getLinkContext().getLinkUrl()));
startActivity(linkIntent);
break;
case TEXT:
case REEL_SHARE:
Utils.copyText(v.getContext(), directItemModel.getText());
Toast.makeText(v.getContext(), R.string.clipboard_copied, Toast.LENGTH_SHORT).show();
break;
case RAVEN_MEDIA:
case MEDIA:
final ProfileModel user = getUser(directItemModel.getUserId());
Utils.dmDownload(requireContext(), user.getUsername(), DownloadMethod.DOWNLOAD_DIRECT, Collections.singletonList(itemType == DirectItemType.MEDIA ? directItemModel.getMediaModel() : directItemModel.getRavenMediaModel().getMedia()));
Toast.makeText(v.getContext(), R.string.downloader_downloading_media, Toast.LENGTH_SHORT).show();
break;
case STORY_SHARE:
if (directItemModel.getReelShare() != null) {
StoryModel sm = new StoryModel(
directItemModel.getReelShare().getReelId(),
directItemModel.getReelShare().getMedia().getVideoUrl(),
directItemModel.getReelShare().getMedia().getMediaType(),
directItemModel.getTimestamp(),
directItemModel.getReelShare().getReelOwnerName(),
String.valueOf(directItemModel.getReelShare().getReelOwnerId()),
false
);
sm.setVideoUrl(directItemModel.getReelShare().getMedia().getVideoUrl());
StoryModel[] sms = {sm};
startActivity(new Intent(requireContext(), StoryViewer.class)
.putExtra(Constants.EXTRAS_USERNAME, directItemModel.getReelShare().getReelOwnerName())
.putExtra(Constants.EXTRAS_STORIES, sms)
);
} else if (directItemModel.getText() != null && directItemModel.getText().toString().contains("@")) {
searchUsername(directItemModel.getText().toString().split("@")[1].split(" ")[0]);
}
break;
case PLACEHOLDER:
if (directItemModel.getText().toString().contains("@"))
searchUsername(directItemModel.getText().toString().split("@")[1].split(" ")[0]);
break;
default:
Log.d("austin_debug", "unsupported type " + itemType);
}
}
};
final MentionClickListener mentionClickListener = (view, text, isHashtag) -> searchUsername(text);
final MessageItemsAdapter adapter = new MessageItemsAdapter(users, leftUsers, onClickListener, mentionClickListener);
messageList.setAdapter(adapter);
listViewModel = new ViewModelProvider(fragmentActivity).get(DirectItemModelListViewModel.class);
listViewModel.getList().observe(fragmentActivity, adapter::submitList);
new DirectMessageInboxThreadFetcher(threadId, UserInboxDirection.OLDER, null, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
return root;
}
@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == PICK_IMAGE && resultCode == Activity.RESULT_OK) {
if (data == null || data.getData() == null) {
Log.w(TAG, "data is null!");
return;
}
final Uri uri = data.getData();
sendImage(uri);
}
}
@Override
public void onDestroy() {
super.onDestroy();
listViewModel.getList().postValue(Collections.emptyList());
}
private void sendText(final String text) {
final DirectThreadBroadcaster.TextBroadcastOptions options;
try {
options = new DirectThreadBroadcaster.TextBroadcastOptions(text);
} catch (UnsupportedEncodingException e) {
Log.e(TAG, "Error", e);
return;
}
broadcast(options, result -> {
if (result == null || result.getResponseCode() != HttpURLConnection.HTTP_OK) {
Toast.makeText(requireContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
return;
}
binding.commentText.setText("");
// binding.commentText.clearFocus();
hasSentSomething = true;
new DirectMessageInboxThreadFetcher(threadId, UserInboxDirection.OLDER, null, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
});
}
private void sendImage(final Uri imageUri) {
try(InputStream inputStream = requireContext().getContentResolver().openInputStream(imageUri)) {
final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
// Upload Image
final ImageUploader imageUploader = new ImageUploader();
imageUploader.setOnTaskCompleteListener(response -> {
if (response == null || response.getResponseCode() != HttpURLConnection.HTTP_OK) {
Toast.makeText(requireContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
if (response != null && response.getResponse() != null) {
Log.e(TAG, response.getResponse().toString());
}
return;
}
final JSONObject responseJson = response.getResponse();
try {
final String uploadId = responseJson.getString("upload_id");
// Broadcast
final DirectThreadBroadcaster.ImageBroadcastOptions options = new DirectThreadBroadcaster.ImageBroadcastOptions(true, uploadId);
hasSentSomething = true;
broadcast(options, onBroadcastCompleteListener -> new DirectMessageInboxThreadFetcher(threadId, UserInboxDirection.OLDER, null, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR));
} catch (JSONException e) {
Log.e(TAG, "Error parsing json response", e);
}
});
final ImageUploadOptions options = ImageUploadOptions.builder(bitmap).build();
imageUploader.execute(options);
}
catch (IOException e) {
Log.e(TAG, "Error opening file", e);
}
}
private void broadcast(final DirectThreadBroadcaster.BroadcastOptions broadcastOptions, final DirectThreadBroadcaster.OnBroadcastCompleteListener listener) {
final DirectThreadBroadcaster broadcaster = new DirectThreadBroadcaster(threadId);
broadcaster.setOnTaskCompleteListener(listener);
broadcaster.execute(broadcastOptions);
}
@NonNull
private ProfileModel getUser(final long userId) {
ProfileModel result = myProfileHolder;
for (final ProfileModel user : users) {
if (Long.toString(userId).equals(user.getId())) result = user;
}
for (final ProfileModel leftUser : leftUsers) {
if (Long.toString(userId).equals(leftUser.getId())) result = leftUser;
}
return result;
}
private void searchUsername(final String text) {
startActivity(new Intent(requireContext(), ProfileViewer.class).putExtra(Constants.EXTRAS_USERNAME, text));
}
public static class DirectItemModelListViewModel extends ViewModel {
private MutableLiveData<List<DirectItemModel>> list;
public MutableLiveData<List<DirectItemModel>> getList() {
if (list == null) {
list = new MutableLiveData<>();
}
return list;
}
}
}

62
app/src/main/java/awais/instagrabber/models/ImageUploadOptions.java

@ -0,0 +1,62 @@
package awais.instagrabber.models;
import android.graphics.Bitmap;
public class ImageUploadOptions {
private final Bitmap bitmap;
private boolean isSidecar;
private String waterfallId;
public static class Builder {
private Bitmap bitmap;
private boolean isSidecar;
private String waterfallId;
public Builder(final Bitmap bitmap) {
this.bitmap = bitmap;
}
public Builder setBitmap(final Bitmap bitmap) {
this.bitmap = bitmap;
return this;
}
public Builder setIsSidecar(final boolean isSidecar) {
this.isSidecar = isSidecar;
return this;
}
public Builder setWaterfallId(final String waterfallId) {
this.waterfallId = waterfallId;
return this;
}
public ImageUploadOptions build() {
return new ImageUploadOptions(bitmap, isSidecar, waterfallId);
}
}
public static Builder builder(final Bitmap file) {
return new Builder(file);
}
private ImageUploadOptions(final Bitmap bitmap,
final boolean isSidecar,
final String waterfallId) {
this.bitmap = bitmap;
this.isSidecar = isSidecar;
this.waterfallId = waterfallId;
}
public Bitmap getBitmap() {
return bitmap;
}
public boolean isSidecar() {
return isSidecar;
}
public String getWaterfallId() {
return waterfallId;
}
}

24
app/src/main/java/awais/instagrabber/models/ProfileModel.java

@ -31,6 +31,14 @@ public final class ProfileModel implements Serializable {
this.requested = requested; this.requested = requested;
} }
public static ProfileModel getDefaultProfileModel() {
return new ProfileModel(false, false, false, null, null, null, null, null, null, null, 0, 0, 0, false, false, false, false);
}
public static ProfileModel getDefaultProfileModel(final String userId) {
return new ProfileModel(false, false, false, userId, null, null, null, null, null, null, 0, 0, 0, false, false, false, false);
}
public boolean isPrivate() { public boolean isPrivate() {
return isPrivate; return isPrivate;
} }
@ -71,17 +79,25 @@ public final class ProfileModel implements Serializable {
return hdProfilePic; return hdProfilePic;
} }
public long getPostCount() { return postCount; }
public long getPostCount() {
return postCount;
}
public long getFollowersCount() { return followersCount; }
public long getFollowersCount() {
return followersCount;
}
public long getFollowingCount() { public long getFollowingCount() {
return followingCount; return followingCount;
} }
public boolean getFollowing() { return following; }
public boolean getFollowing() {
return following;
}
public boolean getRestricted() { return restricted; }
public boolean getRestricted() {
return restricted;
}
public boolean getBlocked() { public boolean getBlocked() {
return blocked; return blocked;

18
app/src/main/java/awais/instagrabber/models/direct_messages/InboxThreadModel.java

@ -1,5 +1,7 @@
package awais.instagrabber.models.direct_messages; package awais.instagrabber.models.direct_messages;
import androidx.core.util.ObjectsCompat;
import java.io.Serializable; import java.io.Serializable;
import awais.instagrabber.models.ProfileModel; import awais.instagrabber.models.ProfileModel;
@ -123,7 +125,7 @@ public final class InboxThreadModel implements Serializable {
return canonical; return canonical;
} }
public boolean isHasOlder() {
public boolean hasOlder() {
return hasOlder; return hasOlder;
} }
@ -142,4 +144,18 @@ public final class InboxThreadModel implements Serializable {
public long getLastActivityAt() { public long getLastActivityAt() {
return lastActivityAt; return lastActivityAt;
} }
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final InboxThreadModel that = (InboxThreadModel) o;
return ObjectsCompat.equals(threadId, that.threadId) &&
ObjectsCompat.equals(threadV2Id, that.threadV2Id);
}
@Override
public int hashCode() {
return ObjectsCompat.hash(threadId, threadV2Id);
}
} }

14
app/src/main/java/awais/instagrabber/models/enums/UserInboxDirection.java

@ -1,6 +1,16 @@
package awais.instagrabber.models.enums; package awais.instagrabber.models.enums;
public enum UserInboxDirection { public enum UserInboxDirection {
OLDER,
NEWER,
OLDER("older"),
NEWER("newer");
private final String value;
UserInboxDirection(final String value) {
this.value = value;
}
public String getValue() {
return value;
}
} }

53
app/src/main/java/awais/instagrabber/utils/Utils.java

@ -42,6 +42,7 @@ import androidx.fragment.app.FragmentManager;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
import org.jsoup.internal.StringUtil;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
@ -57,6 +58,8 @@ import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import javax.crypto.Mac; import javax.crypto.Mac;
@ -101,6 +104,7 @@ import static awais.instagrabber.utils.Constants.FOLDER_PATH;
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO; import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
public final class Utils { public final class Utils {
private static final String TAG = "Utils";
public static LogCollector logCollector; public static LogCollector logCollector;
public static SettingsHelper settingsHelper; public static SettingsHelper settingsHelper;
public static DataBox dataBox; public static DataBox dataBox;
@ -121,10 +125,13 @@ public final class Utils {
public static void setupCookies(final String cookieRaw) { public static void setupCookies(final String cookieRaw) {
final CookieStore cookieStore = NET_COOKIE_MANAGER.getCookieStore(); final CookieStore cookieStore = NET_COOKIE_MANAGER.getCookieStore();
if (cookieRaw == "LOGOUT") {
if (cookieStore == null || isEmpty(cookieRaw)) {
return;
}
if (cookieRaw.equals("LOGOUT")) {
cookieStore.removeAll(); cookieStore.removeAll();
return;
} }
else if (cookieRaw != null && !isEmpty(cookieRaw)) {
try { try {
final URI uri1 = new URI("https://instagram.com"); final URI uri1 = new URI("https://instagram.com");
final URI uri2 = new URI("https://instagram.com/"); final URI uri2 = new URI("https://instagram.com/");
@ -145,7 +152,6 @@ public final class Utils {
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
} }
} }
}
@Nullable @Nullable
public static String getUserIdFromCookie(final String cookie) { public static String getUserIdFromCookie(final String cookie) {
@ -1174,19 +1180,19 @@ public final class Utils {
public static String sign(final String message) { public static String sign(final String message) {
try { try {
Mac hasher = Mac.getInstance("HmacSHA256");
final Mac hasher = Mac.getInstance("HmacSHA256");
hasher.init(new SecretKeySpec(Constants.SIGNATURE_KEY.getBytes(), "HmacSHA256")); hasher.init(new SecretKeySpec(Constants.SIGNATURE_KEY.getBytes(), "HmacSHA256"));
byte[] hash = hasher.doFinal(message.getBytes()); byte[] hash = hasher.doFinal(message.getBytes());
StringBuffer hexString = new StringBuffer();
for (int i = 0; i < hash.length; i++) {
String hex = Integer.toHexString(0xff & hash[i]);
final StringBuilder hexString = new StringBuilder();
for (byte b : hash) {
final String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) hexString.append('0'); if (hex.length() == 1) hexString.append('0');
hexString.append(hex); hexString.append(hex);
} }
return "ig_sig_key_version="+Constants.SIGNATURE_VERSION+"&signed_body=" + hexString.toString() + "." + message; return "ig_sig_key_version="+Constants.SIGNATURE_VERSION+"&signed_body=" + hexString.toString() + "." + message;
} }
catch (Throwable e) {
Log.e("austin_debug", "sign: ", e);
catch (Exception e) {
Log.e(TAG, "Error signing", e);
return null; return null;
} }
} }
@ -1355,4 +1361,33 @@ public final class Utils {
return null; return null;
} }
public static void setConnectionHeaders(final HttpURLConnection connection, final Map<String, String> headers) {
if (connection == null || headers == null || headers.isEmpty()) {
return;
}
for (Map.Entry<String, String> header : headers.entrySet()) {
connection.setRequestProperty(header.getKey(), header.getValue());
}
}
public static String getQueryString(final Map<String, String> queryParamsMap) {
if (queryParamsMap == null || queryParamsMap.isEmpty()) {
return "";
}
final Set<Map.Entry<String, String>> params = queryParamsMap.entrySet();
final StringBuilder builder = new StringBuilder();
for (final Map.Entry<String, String> param : params) {
if (isEmpty(param.getKey())) {
continue;
}
if (builder.length() != 0) {
builder.append("&");
}
builder.append(param.getKey());
builder.append("=");
builder.append(param.getValue() != null ? param.getValue() : "");
}
return builder.toString();
}
} }

10
app/src/main/res/drawable/ic_image_24.xml

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M19,5v14L5,19L5,5h14m0,-2L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM14.14,11.86l-3,3.87L9,13.14 6,17h12l-3.86,-5.14z"/>
</vector>

10
app/src/main/res/drawable/ic_send_24.xml

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z"/>
</vector>

54
app/src/main/res/layout/activity_direct_messages.xml

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/homeCoordinator"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:fitsSystemWindows="true"
tools:context=".activities.DirectMessagesActivity">
<fragment
android:id="@+id/direct_messages_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
app:navGraph="@navigation/direct_messages_nav_graph"
tools:ignore="FragmentTagUsage" />
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:liftOnScroll="true">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/collapsingToolbarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways|exitUntilCollapsed|snap"
app:scrimAnimationDuration="10">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<TextView
android:id="@+id/toolbar_title"
style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:text="@string/app_name" />
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

10
app/src/main/res/layout/fragment_direct_messages_inbox.xml

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/inbox_list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

48
app/src/main/res/layout/activity_dms.xml → app/src/main/res/layout/fragment_direct_messages_thread.xml

@ -1,25 +1,18 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".activities.DirectMessages">
<include
android:id="@+id/toolbar"
android:layout_weight="1"
layout="@layout/layout_include_toolbar" />
android:orientation="vertical">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshLayout" android:id="@+id/swipeRefreshLayout"
android:layout_weight="8"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="0dp"
android:layout_weight="1">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvDirectMessages"
android:id="@+id/messageList"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
@ -30,27 +23,46 @@
<EditText <EditText
android:id="@+id/commentText" android:id="@+id/commentText"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="2"
android:paddingLeft="8dp"
android:layout_weight="1"
android:gravity="bottom" android:gravity="bottom"
android:hint="@string/dm_hint" android:hint="@string/dm_hint"
android:importantForAutofill="no"
android:inputType="textMultiLine" android:inputType="textMultiLine"
android:maxLength="2200" android:maxLength="2200"
android:maxLines="10" android:maxLines="10"
android:visibility="gone"
android:paddingStart="8dp"
android:paddingLeft="8dp"
android:paddingEnd="4dp"
android:paddingRight="4dp"
android:scrollHorizontally="false" /> android:scrollHorizontally="false" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="?selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:padding="4dp"
app:srcCompat="@drawable/ic_image_24" />
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/commentSend" android:id="@+id/commentSend"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="?selectableItemBackgroundBorderless" android:background="?selectableItemBackgroundBorderless"
android:clickable="true" android:clickable="true"
android:focusable="true"
android:paddingStart="4dp"
android:paddingLeft="4dp"
android:paddingTop="4dp"
android:paddingEnd="8dp"
android:paddingRight="8dp" android:paddingRight="8dp"
android:visibility="gone"
app:srcCompat="@android:drawable/ic_menu_send" />
android:paddingBottom="4dp"
app:srcCompat="@drawable/ic_send_24" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

3
app/src/main/res/layout/item_message_item.xml

@ -4,8 +4,7 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal"
tools:viewBindingIgnore="true">
android:orientation="horizontal">
<awais.instagrabber.customviews.CircularImageView <awais.instagrabber.customviews.CircularImageView
android:id="@+id/ivProfilePic" android:id="@+id/ivProfilePic"

26
app/src/main/res/navigation/direct_messages_nav_graph.xml

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/direct_messages_nav_graph"
app:startDestination="@id/directMessagesInboxFragment">
<fragment
android:id="@+id/directMessagesInboxFragment"
android:name="awais.instagrabber.fragments.directmessages.DirectMessageInboxFragment"
android:label="DirectMessagesInboxFragment" >
<action
android:id="@+id/action_dMInboxFragment_to_dMThreadFragment"
app:destination="@id/directMessagesThreadFragment" />
</fragment>
<fragment
android:id="@+id/directMessagesThreadFragment"
android:name="awais.instagrabber.fragments.directmessages.DirectMessageThreadFragment"
android:label="DirectMessagesThreadFragment">
<argument
android:name="threadId"
app:argType="string" />
<argument
android:name="title"
app:argType="string" />
</fragment>
</navigation>

3
app/src/main/res/values/strings.xml

@ -181,7 +181,7 @@
<string name="comment_viewer_like_comment">Like comment</string> <string name="comment_viewer_like_comment">Like comment</string>
<string name="comment_viewer_unlike_comment">Unlike comment</string> <string name="comment_viewer_unlike_comment">Unlike comment</string>
<string name="comment_viewer_delete_comment">Delete comment</string> <string name="comment_viewer_delete_comment">Delete comment</string>
<string name="comment_send_empty_comment">No empty comments, dawg!</string>
<string name="comment_send_empty_comment">No empty comments!</string>
<string name="comment_view_mention_user_search">Do you want to search the username?</string> <string name="comment_view_mention_user_search">Do you want to search the username?</string>
<string name="comment_view_mention_hash_search">Do you want to search the hashtag?</string> <string name="comment_view_mention_hash_search">Do you want to search the hashtag?</string>
<string name="comment_view_mention_location_search">Do you want to search the location?</string> <string name="comment_view_mention_location_search">Do you want to search the location?</string>
@ -213,4 +213,5 @@
<string name="use_amoled_dark_theme">Use AMOLED mode for Dark theme</string> <string name="use_amoled_dark_theme">Use AMOLED mode for Dark theme</string>
<string name="action_notif">Activity</string> <string name="action_notif">Activity</string>
<string name="license" translatable="false">InstaGrabber\nCopyright (C) 2019 AWAiS\nCopyright (C) 2020 Austin Huang, Ammar Githam\n\nThis program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. See https://www.gnu.org/licenses/.\n\nTHIS SOFTWARE IS PROVIDED BY THE AUTHOR \'\'AS IS\'\' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nSee project page for third-party attributions.</string> <string name="license" translatable="false">InstaGrabber\nCopyright (C) 2019 AWAiS\nCopyright (C) 2020 Austin Huang, Ammar Githam\n\nThis program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. See https://www.gnu.org/licenses/.\n\nTHIS SOFTWARE IS PROVIDED BY THE AUTHOR \'\'AS IS\'\' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nSee project page for third-party attributions.</string>
<string name="select_picture">Select Picture</string>
</resources> </resources>

3
build.gradle

@ -6,6 +6,9 @@ buildscript {
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.0.1' classpath 'com.android.tools.build:gradle:4.0.1'
def nav_version = "2.3.0"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
} }
} }

Loading…
Cancel
Save