diff --git a/app/src/main/java/awais/instagrabber/activities/directmessages/DirectMessageThread.java b/app/src/main/java/awais/instagrabber/activities/directmessages/DirectMessageThread.java index bbec718a..7a374a2d 100644 --- a/app/src/main/java/awais/instagrabber/activities/directmessages/DirectMessageThread.java +++ b/app/src/main/java/awais/instagrabber/activities/directmessages/DirectMessageThread.java @@ -2,11 +2,10 @@ package awais.instagrabber.activities.directmessages; import android.app.Activity; import android.content.Intent; -import android.database.Cursor; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; -import android.provider.OpenableColumns; +import android.os.ParcelFileDescriptor; import android.text.TextUtils; import android.util.Log; import android.view.View; @@ -16,8 +15,13 @@ import androidx.annotation.Nullable; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import org.json.JSONException; +import org.json.JSONObject; + import java.io.FileNotFoundException; import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -29,11 +33,17 @@ import awais.instagrabber.activities.PostViewer; import awais.instagrabber.activities.ProfileViewer; import awais.instagrabber.activities.StoryViewer; 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.customviews.helpers.RecyclerLazyLoader; import awais.instagrabber.databinding.ActivityDmsBinding; import awais.instagrabber.interfaces.FetchListener; +import awais.instagrabber.models.ImageUploadOptions; import awais.instagrabber.models.PostModel; import awais.instagrabber.models.ProfileModel; import awais.instagrabber.models.StoryModel; @@ -102,23 +112,12 @@ public final class DirectMessageThread extends BaseLanguageActivity { }; private final View.OnClickListener clickListener = v -> { 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(); return; } - final CommentAction action = new CommentAction(dmsBinding.commentText.getText().toString(), threadId); - 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(); + sendText(text); return; } if (v == dmsBinding.image) { @@ -127,7 +126,6 @@ public final class DirectMessageThread extends BaseLanguageActivity { intent.setAction(Intent.ACTION_GET_CONTENT); startActivityForResult(Intent.createChooser(intent, getString(R.string.select_picture)), PICK_IMAGE); } - }; @Override @@ -228,27 +226,13 @@ public final class DirectMessageThread extends BaseLanguageActivity { @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); - if (requestCode == PICK_IMAGE && resultCode == Activity.RESULT_OK) { + if (requestCode == PICK_IMAGE && resultCode == Activity.RESULT_OK) { if (data == null || data.getData() == null) { Log.w(TAG, "data is null!"); return; } - Cursor cursor = null; - try { - final Uri uri = data.getData(); - cursor = getContentResolver().query(uri, null, null, null, null); - 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(); - } - } + final Uri uri = data.getData(); + sendImage(uri); } } @@ -271,4 +255,71 @@ public final class DirectMessageThread extends BaseLanguageActivity { private void searchUsername(final String 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); + } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/asyncs/ImageUploader.java b/app/src/main/java/awais/instagrabber/asyncs/ImageUploader.java new file mode 100644 index 00000000..41454158 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/asyncs/ImageUploader.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 { + 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 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 createPhotoRuploadParams(final ImageUploadOptions options, final String uploadId) { + final Map 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 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 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; + } + } +} diff --git a/app/src/main/java/awais/instagrabber/asyncs/direct_messages/CommentAction.java b/app/src/main/java/awais/instagrabber/asyncs/direct_messages/CommentAction.java deleted file mode 100644 index 6b6cc08c..00000000 --- a/app/src/main/java/awais/instagrabber/asyncs/direct_messages/CommentAction.java +++ /dev/null @@ -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 { - 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); - } -} diff --git a/app/src/main/java/awais/instagrabber/asyncs/direct_messages/DirectThreadBroadcaster.java b/app/src/main/java/awais/instagrabber/asyncs/direct_messages/DirectThreadBroadcaster.java new file mode 100644 index 00000000..98542cbd --- /dev/null +++ b/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 { + 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 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 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 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 getFormMap() { + final Map 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; + } + } +} diff --git a/app/src/main/java/awais/instagrabber/models/ImageUploadOptions.java b/app/src/main/java/awais/instagrabber/models/ImageUploadOptions.java new file mode 100644 index 00000000..c7006fc2 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/models/ImageUploadOptions.java @@ -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; + } +} diff --git a/app/src/main/java/awais/instagrabber/utils/Utils.java b/app/src/main/java/awais/instagrabber/utils/Utils.java index ee218b3d..033b3f26 100755 --- a/app/src/main/java/awais/instagrabber/utils/Utils.java +++ b/app/src/main/java/awais/instagrabber/utils/Utils.java @@ -57,6 +57,7 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.regex.Pattern; 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; public final class Utils { + private static final String TAG = "Utils"; public static LogCollector logCollector; public static SettingsHelper settingsHelper; public static DataBox dataBox; @@ -1179,19 +1181,19 @@ public final class Utils { public static String sign(final String message) { try { - Mac hasher = Mac.getInstance("HmacSHA256"); + final Mac hasher = Mac.getInstance("HmacSHA256"); hasher.init(new SecretKeySpec(Constants.SIGNATURE_KEY.getBytes(), "HmacSHA256")); 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'); hexString.append(hex); } 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; } } @@ -1360,4 +1362,13 @@ public final class Utils { return null; } + + public static void setConnectionHeaders(final HttpURLConnection connection, final Map headers) { + if (connection == null || headers == null || headers.isEmpty()) { + return; + } + for (Map.Entry header : headers.entrySet()) { + connection.setRequestProperty(header.getKey(), header.getValue()); + } + } } \ No newline at end of file