mirror of
https://github.com/KokaKiwi/BarInsta
synced 2024-11-22 14:47:29 +00:00
Allow sending image in dms
This commit streamlines the broadcast flow to handle both text and image messages in dms.
This commit is contained in:
parent
066a453aa8
commit
650062646d
@ -2,11 +2,10 @@ package awais.instagrabber.activities.directmessages;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.database.Cursor;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.provider.OpenableColumns;
|
import android.os.ParcelFileDescriptor;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@ -16,8 +15,13 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -29,11 +33,17 @@ import awais.instagrabber.activities.PostViewer;
|
|||||||
import awais.instagrabber.activities.ProfileViewer;
|
import awais.instagrabber.activities.ProfileViewer;
|
||||||
import awais.instagrabber.activities.StoryViewer;
|
import awais.instagrabber.activities.StoryViewer;
|
||||||
import awais.instagrabber.adapters.MessageItemsAdapter;
|
import awais.instagrabber.adapters.MessageItemsAdapter;
|
||||||
import awais.instagrabber.asyncs.direct_messages.CommentAction;
|
import awais.instagrabber.asyncs.ImageUploader;
|
||||||
|
import awais.instagrabber.asyncs.direct_messages.DirectThreadBroadcaster;
|
||||||
|
import awais.instagrabber.asyncs.direct_messages.DirectThreadBroadcaster.BroadcastOptions;
|
||||||
|
import awais.instagrabber.asyncs.direct_messages.DirectThreadBroadcaster.ImageBroadcastOptions;
|
||||||
|
import awais.instagrabber.asyncs.direct_messages.DirectThreadBroadcaster.OnBroadcastCompleteListener;
|
||||||
|
import awais.instagrabber.asyncs.direct_messages.DirectThreadBroadcaster.TextBroadcastOptions;
|
||||||
import awais.instagrabber.asyncs.direct_messages.UserInboxFetcher;
|
import awais.instagrabber.asyncs.direct_messages.UserInboxFetcher;
|
||||||
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
|
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
|
||||||
import awais.instagrabber.databinding.ActivityDmsBinding;
|
import awais.instagrabber.databinding.ActivityDmsBinding;
|
||||||
import awais.instagrabber.interfaces.FetchListener;
|
import awais.instagrabber.interfaces.FetchListener;
|
||||||
|
import awais.instagrabber.models.ImageUploadOptions;
|
||||||
import awais.instagrabber.models.PostModel;
|
import awais.instagrabber.models.PostModel;
|
||||||
import awais.instagrabber.models.ProfileModel;
|
import awais.instagrabber.models.ProfileModel;
|
||||||
import awais.instagrabber.models.StoryModel;
|
import awais.instagrabber.models.StoryModel;
|
||||||
@ -102,23 +112,12 @@ public final class DirectMessageThread extends BaseLanguageActivity {
|
|||||||
};
|
};
|
||||||
private final View.OnClickListener clickListener = v -> {
|
private final View.OnClickListener clickListener = v -> {
|
||||||
if (v == dmsBinding.commentSend) {
|
if (v == dmsBinding.commentSend) {
|
||||||
if (Utils.isEmpty(dmsBinding.commentText.getText().toString())) {
|
final String text = dmsBinding.commentText.getText().toString();
|
||||||
|
if (Utils.isEmpty(text)) {
|
||||||
Toast.makeText(getApplicationContext(), R.string.comment_send_empty_comment, Toast.LENGTH_SHORT).show();
|
Toast.makeText(getApplicationContext(), R.string.comment_send_empty_comment, Toast.LENGTH_SHORT).show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final CommentAction action = new CommentAction(dmsBinding.commentText.getText().toString(), threadId);
|
sendText(text);
|
||||||
action.setOnTaskCompleteListener(result -> {
|
|
||||||
if (!result) {
|
|
||||||
Toast.makeText(getApplicationContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
dmsBinding.commentText.setText("");
|
|
||||||
dmsBinding.commentText.clearFocus();
|
|
||||||
directItemModels.clear();
|
|
||||||
messageItemsAdapter.notifyDataSetChanged();
|
|
||||||
new UserInboxFetcher(threadId, UserInboxDirection.OLDER, null, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
|
||||||
});
|
|
||||||
action.execute();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (v == dmsBinding.image) {
|
if (v == dmsBinding.image) {
|
||||||
@ -127,7 +126,6 @@ public final class DirectMessageThread extends BaseLanguageActivity {
|
|||||||
intent.setAction(Intent.ACTION_GET_CONTENT);
|
intent.setAction(Intent.ACTION_GET_CONTENT);
|
||||||
startActivityForResult(Intent.createChooser(intent, getString(R.string.select_picture)), PICK_IMAGE);
|
startActivityForResult(Intent.createChooser(intent, getString(R.string.select_picture)), PICK_IMAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -233,22 +231,8 @@ public final class DirectMessageThread extends BaseLanguageActivity {
|
|||||||
Log.w(TAG, "data is null!");
|
Log.w(TAG, "data is null!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Cursor cursor = null;
|
|
||||||
try {
|
|
||||||
final Uri uri = data.getData();
|
final Uri uri = data.getData();
|
||||||
cursor = getContentResolver().query(uri, null, null, null, null);
|
sendImage(uri);
|
||||||
if (cursor != null) {
|
|
||||||
final int contentLength = cursor.getColumnIndex(OpenableColumns.SIZE);
|
|
||||||
final InputStream inputStream = getContentResolver().openInputStream(uri);
|
|
||||||
// TODO Handle image upload
|
|
||||||
}
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
Log.e(TAG, "Error opening InputStream", e);
|
|
||||||
} finally {
|
|
||||||
if (cursor != null) {
|
|
||||||
cursor.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -271,4 +255,71 @@ public final class DirectMessageThread extends BaseLanguageActivity {
|
|||||||
private void searchUsername(final String text) {
|
private void searchUsername(final String text) {
|
||||||
startActivity(new Intent(getApplicationContext(), ProfileViewer.class).putExtra(Constants.EXTRAS_USERNAME, text));
|
startActivity(new Intent(getApplicationContext(), ProfileViewer.class).putExtra(Constants.EXTRAS_USERNAME, text));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void sendText(final String text) {
|
||||||
|
final TextBroadcastOptions options;
|
||||||
|
try {
|
||||||
|
options = new TextBroadcastOptions(text);
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
Log.e(TAG, "Error", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
broadcast(options, result -> {
|
||||||
|
if (result == null || result.getResponseCode() != HttpURLConnection.HTTP_OK) {
|
||||||
|
Toast.makeText(getApplicationContext(), R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dmsBinding.commentText.setText("");
|
||||||
|
dmsBinding.commentText.clearFocus();
|
||||||
|
directItemModels.clear();
|
||||||
|
messageItemsAdapter.notifyDataSetChanged();
|
||||||
|
new UserInboxFetcher(threadId, UserInboxDirection.OLDER, null, fetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendImage(final Uri imageUri) {
|
||||||
|
try {
|
||||||
|
final ParcelFileDescriptor fileDescriptor = getContentResolver().openFileDescriptor(imageUri, "r");
|
||||||
|
if (fileDescriptor == null) {
|
||||||
|
Log.e(TAG, "fileDescriptor is null!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final long contentLength = fileDescriptor.getStatSize();
|
||||||
|
final InputStream inputStream = getContentResolver().openInputStream(imageUri);
|
||||||
|
// Upload Image
|
||||||
|
final ImageUploader imageUploader = new ImageUploader();
|
||||||
|
imageUploader.setOnTaskCompleteListener(response -> {
|
||||||
|
if (response == null || response.getResponseCode() != HttpURLConnection.HTTP_OK) {
|
||||||
|
Toast.makeText(getApplicationContext(), 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 ImageBroadcastOptions options = new ImageBroadcastOptions(true, uploadId);
|
||||||
|
broadcast(options, onBroadcastCompleteListener -> {
|
||||||
|
directItemModels.clear();
|
||||||
|
messageItemsAdapter.notifyDataSetChanged();
|
||||||
|
new UserInboxFetcher(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(inputStream, contentLength).build();
|
||||||
|
imageUploader.execute(options);
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
Log.e(TAG, "Error opening InputStream", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void broadcast(final BroadcastOptions broadcastOptions, final OnBroadcastCompleteListener listener) {
|
||||||
|
final DirectThreadBroadcaster broadcaster = new DirectThreadBroadcaster(threadId);
|
||||||
|
broadcaster.setOnTaskCompleteListener(listener);
|
||||||
|
broadcaster.execute(broadcastOptions);
|
||||||
|
}
|
||||||
}
|
}
|
167
app/src/main/java/awais/instagrabber/asyncs/ImageUploader.java
Normal file
167
app/src/main/java/awais/instagrabber/asyncs/ImageUploader.java
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
package awais.instagrabber.asyncs;
|
||||||
|
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
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;
|
||||||
|
try {
|
||||||
|
final ImageUploadOptions options = imageUploadOptions[0];
|
||||||
|
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 contentLength = String.valueOf(options.getContentLength());
|
||||||
|
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 = connection.getOutputStream();
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
int n;
|
||||||
|
inputStream = options.getInputStream();
|
||||||
|
while (-1 != (n = inputStream.read(buffer))) {
|
||||||
|
out.write(buffer, 0, n);
|
||||||
|
}
|
||||||
|
out.flush();
|
||||||
|
final int responseCode = connection.getResponseCode();
|
||||||
|
Log.d(TAG, "response: " + responseCode);
|
||||||
|
if (responseCode != HttpURLConnection.HTTP_OK) {
|
||||||
|
return new ImageUploadResponse(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 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,84 +0,0 @@
|
|||||||
package awais.instagrabber.asyncs.direct_messages;
|
|
||||||
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.net.URLEncoder;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import awais.instagrabber.utils.Constants;
|
|
||||||
import awais.instagrabber.utils.Utils;
|
|
||||||
|
|
||||||
import static awais.instagrabber.utils.Utils.settingsHelper;
|
|
||||||
|
|
||||||
public class CommentAction extends AsyncTask<Void, Void, Boolean> {
|
|
||||||
private final String text;
|
|
||||||
private final String threadId;
|
|
||||||
|
|
||||||
private OnTaskCompleteListener listener;
|
|
||||||
|
|
||||||
public CommentAction(String text, String threadId) {
|
|
||||||
this.text = text;
|
|
||||||
this.threadId = threadId;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Boolean doInBackground(Void... lmao) {
|
|
||||||
boolean ok = false;
|
|
||||||
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(text, "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 ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(final Boolean result) {
|
|
||||||
if (listener != null) {
|
|
||||||
listener.onTaskComplete(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOnTaskCompleteListener(final OnTaskCompleteListener listener) {
|
|
||||||
if (listener != null) {
|
|
||||||
this.listener = listener;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface OnTaskCompleteListener {
|
|
||||||
void onTaskComplete(boolean ok);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
package awais.instagrabber.models;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
public class ImageUploadOptions {
|
||||||
|
private InputStream inputStream;
|
||||||
|
private long contentLength;
|
||||||
|
private boolean isSidecar;
|
||||||
|
private String waterfallId;
|
||||||
|
|
||||||
|
public static class Builder {
|
||||||
|
private InputStream inputStream;
|
||||||
|
private long contentLength;
|
||||||
|
private boolean isSidecar;
|
||||||
|
private String waterfallId;
|
||||||
|
|
||||||
|
public Builder(final InputStream inputStream, final long contentLength) {
|
||||||
|
this.inputStream = inputStream;
|
||||||
|
this.contentLength = contentLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setInputStream(final InputStream inputStream) {
|
||||||
|
this.inputStream = inputStream;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder setContentLength(final long contentLength) {
|
||||||
|
this.contentLength = contentLength;
|
||||||
|
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(inputStream, contentLength, isSidecar, waterfallId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder builder(final InputStream inputStream, final long contentLength) {
|
||||||
|
return new Builder(inputStream, contentLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ImageUploadOptions(final InputStream inputStream,
|
||||||
|
final long contentLength,
|
||||||
|
final boolean isSidecar,
|
||||||
|
final String waterfallId) {
|
||||||
|
this.inputStream = inputStream;
|
||||||
|
this.contentLength = contentLength;
|
||||||
|
this.isSidecar = isSidecar;
|
||||||
|
this.waterfallId = waterfallId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputStream getInputStream() {
|
||||||
|
return inputStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getContentLength() {
|
||||||
|
return contentLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSidecar() {
|
||||||
|
return isSidecar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getWaterfallId() {
|
||||||
|
return waterfallId;
|
||||||
|
}
|
||||||
|
}
|
@ -57,6 +57,7 @@ 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.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import javax.crypto.Mac;
|
import javax.crypto.Mac;
|
||||||
@ -101,6 +102,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;
|
||||||
@ -1179,19 +1181,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();
|
final StringBuilder hexString = new StringBuilder();
|
||||||
for (int i = 0; i < hash.length; i++) {
|
for (byte b : hash) {
|
||||||
String hex = Integer.toHexString(0xff & hash[i]);
|
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) {
|
catch (Exception e) {
|
||||||
Log.e("austin_debug", "sign: ", e);
|
Log.e(TAG, "Error signing", e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1360,4 +1362,13 @@ 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user