Browse Source
Allow sending image in dms
Allow sending image in dms
This commit streamlines the broadcast flow to handle both text and image messages in dms.legacy
Ammar Githam
4 years ago
6 changed files with 553 additions and 125 deletions
-
121app/src/main/java/awais/instagrabber/activities/directmessages/DirectMessageThread.java
-
167app/src/main/java/awais/instagrabber/asyncs/ImageUploader.java
-
84app/src/main/java/awais/instagrabber/asyncs/direct_messages/CommentAction.java
-
207app/src/main/java/awais/instagrabber/asyncs/direct_messages/DirectThreadBroadcaster.java
-
76app/src/main/java/awais/instagrabber/models/ImageUploadOptions.java
-
23app/src/main/java/awais/instagrabber/utils/Utils.java
@ -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; |
|||
} |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue