Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
package app.revanced.integrations.instagram.patches.download;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.DownloadManager;
import android.content.Context;
import android.net.Uri;
import android.os.Environment;
import android.widget.Toast;
import app.revanced.integrations.shared.Utils;
import com.instagram.api.schemas.MediaOptionStyle;
import com.instagram.model.mediasize.ExtendedImageUrl;

import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.List;
import java.util.Objects;

@SuppressWarnings("unused")
public class DownloadPatch {
// To be added in revanced patches
public static String feedOptionItemIconClassName() {
return "";
}

public static String feedItemClassName() {
return "";
}

public static String mediaListSourceClass() {
return "";
}

public static String listFieldName() {
return "";
}

public static Class<?> feedItemIconClass;
public static Class<?> feedItem;
public static Class<?> mediaListSourceClass;

static {
try {
feedItemIconClass = Class.forName(feedOptionItemIconClassName());
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}

try {
feedItem = Class.forName(feedItemClassName());
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}


try {
mediaListSourceClass = Class.forName(mediaListSourceClass());
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}

public static void addDownloadButton(List items) throws IllegalAccessException, InstantiationException,
NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
Object item = feedItem
.getConstructor(MediaOptionStyle.class, feedItemIconClass, CharSequence.class)
// TODO dynamically get the Download icon field
.newInstance(MediaOptionStyle.A05, feedItemIconClass.getField("A0Z").get(feedItemIconClass),
"Download");

items.add(item);
}

public static void downloadPost(Object media, int index, Object p3, Activity act) throws URISyntaxException, NoSuchFieldException, IllegalAccessException, MalformedURLException, InvocationTargetException {
Field mediaField = null;

// Get media field
for (Field field : media.getClass().getFields()) {
if (field.getType() == mediaListSourceClass.getInterfaces()[0]) {
mediaField = field;
}
}

assert mediaField != null;
Object _mediaList = mediaField.get(media);
_mediaList = mediaListSourceClass.cast(_mediaList);

List mediaList = (List) _mediaList.getClass().getField(listFieldName()).get(_mediaList);

if (mediaList != null) {
downloadDialog(mediaList, index, act);
} else {
downloadMedia(media);
}
}

public static void downloadDialog(List mediaList, int index, Activity activity) {
// TODO use instagram dialog
AlertDialog.Builder d = new AlertDialog.Builder(activity)
.setTitle("Download")
.setItems(new String[]{"Current", "Download all"}, (dialogInterface, i) -> {
try {
if (i == 0) {
downloadMedia(mediaList.get(index));
} else {
for (Object media : mediaList) {
downloadMedia(media);
}
}
} catch (URISyntaxException | MalformedURLException | InvocationTargetException |
IllegalAccessException e) {
e.printStackTrace();
Toast.makeText(Utils.getContext(), "Download failed", Toast.LENGTH_SHORT).show();
}
});
d.show();
}

public static void downloadMedia(Object media) throws URISyntaxException, MalformedURLException, InvocationTargetException, IllegalAccessException {
if (isVideo(media)) {
startDownload(getVideoLink(media), Environment.DIRECTORY_MOVIES, "Instagram");
} else {
startDownload(getPhotoLink(media), Environment.DIRECTORY_PICTURES, "Instagram");
}
}

public static void startDownload(String url, String publicFolder, String subpath) throws URISyntaxException, MalformedURLException {
URL link = new URL(url);

String filename = link.getPath().split("/")[link.getPath().split("/").length - 1];
String publicFolderDirectory = Environment.DIRECTORY_PICTURES;
String subPath =subpath + "/" + filename;

File imageFile = new File(Environment.getExternalStoragePublicDirectory(publicFolderDirectory), subPath);

if (imageFile.exists()) {
return;
}

DownloadManager mgr = (DownloadManager) Utils.getContext().getSystemService(Context.DOWNLOAD_SERVICE);

mgr.enqueue(new DownloadManager.Request(Uri.parse(link.toURI().toString()))
.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI |
DownloadManager.Request.NETWORK_MOBILE)
.setAllowedOverRoaming(false)
.setTitle(filename)
.setDescription("Downloading...")
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)
.setDestinationInExternalPublicDir(publicFolderDirectory,
subPath));

Toast.makeText(Utils.getContext(), "Download completed", Toast.LENGTH_SHORT).show();
}

private static String getVideoLink(Object media) {
return "";
}

public static String getPhotoLink(Object media) throws InvocationTargetException, IllegalAccessException {
Method getExtendedImageUrl = null;
URL url = null;

for (Method method : media.getClass().getMethods()) {
if (method.getParameterTypes().length == 0) {
continue;
}

if (method.getReturnType() == ExtendedImageUrl.class
&& method.getParameterTypes()[0] == Context.class) {
getExtendedImageUrl = method;
}
}

assert getExtendedImageUrl != null;
return ((ExtendedImageUrl) Objects
.requireNonNull(getExtendedImageUrl.invoke(media, Utils.getContext())))
.getUrl();
}

public static boolean isVideo(Object media) {
return true;
}

public static void print(Object message) {
System.out.println("BRUH: " + message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,14 @@ public static void printException(@NonNull LogMessage message, @Nullable Throwab
}
}

/**
* Logging to use if {@link BaseSettings#DEBUG} or {@link Utils#getContext()} may not be initialized.
* Normally this method should not be used.
*/
public static void initializationInfo(@NonNull Class<?> callingClass, @NonNull String message) {
Log.i(REVANCED_LOG_PREFIX + callingClass.getSimpleName(), message);
}

/**
* Logging to use if {@link BaseSettings#DEBUG} or {@link Utils#context} may not be initialized.
* Always logs even if Debugging is not enabled.
Expand Down
14 changes: 14 additions & 0 deletions app/src/main/java/app/revanced/integrations/shared/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,20 @@ public static void setClipboard(@NonNull String text) {
clipboard.setPrimaryClip(clip);
}

public static void setContext(Context appContext) {
context = appContext;
// In some apps like TikTok, the Setting classes can load in weird orders due to cyclic class dependencies.
// Calling the regular printDebug method here can cause a Settings context null pointer exception,
// even though the context is already set before the call.
//
// The initialization logger methods do not directly or indirectly
// reference the Context or any Settings and are unaffected by this problem.
//
// Info level also helps debug if a patch hook is called before
// the context is set since debug logging is off by default.
Logger.initializationInfo(Utils.class, "Set context: " + appContext);
}

public static boolean isTablet() {
return context.getResources().getConfiguration().smallestScreenWidthDp >= 600;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.*;

import android.util.Log;
import app.revanced.integrations.twitter.Pref;
import app.revanced.integrations.twitter.Utils;
import app.revanced.integrations.twitter.settings.Settings;
Expand Down Expand Up @@ -38,9 +39,11 @@ private static void immersivePlayer() {
}

public static Object flagInfo(String flag, Object def) {
// Log.d("BRUH", "name: "+flag+" value: "+def.toString()+" type: "+def.getClass().getName());
try {
if (FLAGS.containsKey(flag)) {
Object val = FLAGS.get(flag);
// Log.d("BRUH", "name: "+flag+" value: "+val+" type: "+val.getClass().getName());
return val;
}
} catch (Exception ex) {
Expand All @@ -58,5 +61,11 @@ public static void load() {
addFlag(item[0], Boolean.valueOf(item[1]));
}
}

addFlag("timeline_auto_refresh_on_foreground_timeout_millis", 99999.0);
addFlag("recent_search_limit_count", 10);
addFlag("home_timeline_start_at_top_min_background_minutes", 99999.0);
addFlag("home_timeline_start_at_top_warm_start_min_background_minutes", 99999.0);
addFlag("home_timeline_start_at_top_timeout_length", 99999.0);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package app.revanced.integrations.twitter.patches.customise;

import android.util.Log;

import java.util.List;

public class NavBar {
public static void printList(List list) {
for (Object o: list) {
print(o.getClass().getName());
}
}

public static void print(String a) {
System.out.println("BRUH"+ a);
}
}
7 changes: 7 additions & 0 deletions stub/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
plugins {
alias(libs.plugins.android.library)
id("org.jetbrains.kotlin.android")
}

android {
Expand All @@ -24,4 +25,10 @@ android {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
}
dependencies {
implementation("androidx.core:core-ktx:+")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.instagram.api.schemas;

public enum MediaOptionStyle {
// TODO dynamically get the normal media type field
A05("normal");

MediaOptionStyle(String mediaOptionStyleUnspecified) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.instagram.model.mediasize;

public class ExtendedImageUrl {
public String getUrl() {
return "";
}
}