diff --git a/app/src/main/java/app/revanced/integrations/twitter/Pref.java b/app/src/main/java/app/revanced/integrations/twitter/Pref.java index 5aeecbcbc8..d4bbddb91c 100644 --- a/app/src/main/java/app/revanced/integrations/twitter/Pref.java +++ b/app/src/main/java/app/revanced/integrations/twitter/Pref.java @@ -17,9 +17,11 @@ public class Pref { public static boolean isRoundOffNumbersEnabled() { return Utils.getBooleanPerf(Settings.MISC_ROUND_OFF_NUMBERS); } + public static boolean isChirpFontEnabled() { return Utils.getBooleanPerf(Settings.MISC_FONT); } + public static boolean unShortUrl() { return Utils.getBooleanPerf(Settings.TIMELINE_UNSHORT_URL); } diff --git a/app/src/main/java/app/revanced/integrations/twitter/Utils.java b/app/src/main/java/app/revanced/integrations/twitter/Utils.java index 0bd68027d4..f71becc63a 100644 --- a/app/src/main/java/app/revanced/integrations/twitter/Utils.java +++ b/app/src/main/java/app/revanced/integrations/twitter/Utils.java @@ -6,7 +6,8 @@ import android.content.Context; import android.content.Intent; import android.widget.LinearLayout; -import app.revanced.integrations.shared.settings.Setting; +import app.revanced.integrations.shared.settings.StringSetting; +import app.revanced.integrations.shared.settings.BooleanSetting; import app.revanced.integrations.shared.settings.preference.SharedPrefCategory; import app.revanced.integrations.twitter.settings.Settings; import app.revanced.integrations.twitter.settings.BackupPrefFragment; @@ -16,6 +17,13 @@ import org.json.JSONObject; import java.util.*; import com.google.android.material.tabs.TabLayout$g; +import android.app.DownloadManager; +import android.net.Uri; +import android.os.Build; +import app.revanced.integrations.twitter.Pref; +import android.os.Environment; +import android.content.BroadcastReceiver; +import android.content.IntentFilter; @SuppressWarnings("unused") public class Utils { @@ -102,7 +110,7 @@ public static Boolean setStringPref(String key,String val) { return false; } - public static String getStringPref(Setting setting) { + public static String getStringPref(StringSetting setting) { String value = sp.getString(setting.key, setting.defaultValue); if (value.isBlank()) { return setting.defaultValue; @@ -161,7 +169,7 @@ public static void deleteSharedPrefAB(Context context,boolean flag) { dialog.show(); } - public static Boolean getBooleanPerf(Setting setting) { + public static Boolean getBooleanPerf(BooleanSetting setting) { return sp.getBoolean(setting.key, setting.defaultValue); } public static String getAll(boolean no_flags){ @@ -220,13 +228,37 @@ public static boolean setAll(String jsonString){ return sts; } - public static String[] addPref(String[] prefs, String pref) { String[] bigger = Arrays.copyOf(prefs, prefs.length+1); bigger[prefs.length] = pref; return bigger; } + public static void downloadFile(String url, String filename) { + DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url)); + request.setDescription("Downloading " + filename); + request.setTitle(filename); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + request.allowScanningByMediaScanner(); + request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED); + } + request.setDestinationInExternalPublicDir(Pref.getPublicFolder(), Pref.getVideoFolder(filename)); + DownloadManager manager = (DownloadManager) ctx.getSystemService(Context.DOWNLOAD_SERVICE); + long downloadId = manager.enqueue(request); + ctx.registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1); + if (id == downloadId) { + toast(strRes("exo_download_completed")+": "+filename); + ctx.unregisterReceiver(this); + } + } + }, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); + + } + public static void toast(String msg){ app.revanced.integrations.shared.Utils.showToastShort(msg); } diff --git a/app/src/main/java/app/revanced/integrations/twitter/patches/NativeDownloader.java b/app/src/main/java/app/revanced/integrations/twitter/patches/NativeDownloader.java new file mode 100644 index 0000000000..7f6cc5c926 --- /dev/null +++ b/app/src/main/java/app/revanced/integrations/twitter/patches/NativeDownloader.java @@ -0,0 +1,325 @@ +package app.revanced.integrations.twitter.patches; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.widget.LinearLayout; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.*; +import android.os.Bundle; +import android.view.View; +import android.widget.TextView; +import android.widget.Toast; +import app.revanced.integrations.twitter.Utils; +import app.revanced.integrations.twitter.Pref; + +public class NativeDownloader { + + private static Map CACHE = new HashMap<>(); + private static void setCache(String key,HashMap value){ + CACHE.put(key,value); + } + + private static HashMap getCache(String key){ + return (HashMap)CACHE.get(key); + } + + private static boolean isCached(String key){ + return CACHE.containsKey(key); + } + + private static Map MAPPING = new HashMap<>(); + + private static void setMapping(String key,String value){ + MAPPING.put(key,value); + } + + private static String getMapping(String key){ + return (String)MAPPING.get(key); + } + + private static boolean isMapNotPresent(String key){ + return !(MAPPING.containsKey(key)); + } + + + public static String downloadString(){ + return strRes("piko_pref_native_downloader_alert_title"); + } + + + private static String getExtension(String typ){ + if(typ.equals("video/mp4")){ + return "mp4"; + } + if(typ.equals("video/webm")){ + return "webm"; + } + if(typ.equals("application/x-mpegURL")){ + return "m3u8"; + } + return "jpg"; + } + + private static String getFilename(String postId,String username){ + return username + "_" + String.valueOf(postId); + } + + + private static ArrayList getMedia(Object yghObj){ + ArrayList mediaData = new ArrayList(); + + Class yghClazz = yghObj.getClass(); + Class superClass = yghClazz.getSuperclass(); + + if (superClass.isInstance(yghObj)) { + try { + Object superClassInstance = superClass.cast(yghObj); + List list = null; + + if(isMapNotPresent("F_MEDIALIST")){ + for (Field field : superClass.getDeclaredFields()) { + if (List.class.isAssignableFrom(field.getType())) { + setMapping("F_MEDIALIST",field.getName()); + break; + } + } + } + + Field fieldList = superClass.getDeclaredField(getMapping("F_MEDIALIST")); + fieldList.setAccessible(true); + list = (List) fieldList.get(superClassInstance); + + + if(list == null){ + return mediaData; + } + + if(isMapNotPresent("M_MEDIADATA")){ + Class itemClass = list.get(0).getClass(); + Method itemMethods[] = itemClass.getDeclaredMethods(); + Method method = itemMethods[itemMethods.length -1]; + setMapping("M_MEDIADATA",method.getName()); + + if(isMapNotPresent("F_IMG")){ + for (Field field : itemClass.getDeclaredFields()) { + if (String.class.isAssignableFrom(field.getType())) { + setMapping("F_IMG",field.getName()); + break; + } + } + } + if(isMapNotPresent("F_VID") || isMapNotPresent("F_VID_CDC")){ + Class clazz = method.getReturnType(); + int c=1; + for (Field field : clazz.getDeclaredFields()) { + if (String.class.isAssignableFrom(field.getType())) { + if(c==1){ + setMapping("F_VID",field.getName()); + }else if(c==2){ + setMapping("F_VID_CDC",field.getName()); + break; + } + + c++; + } + } + } + } + + for (Object item : list) { + + Class itemClass = item.getClass(); + + try { + Method method = itemClass.getDeclaredMethod(getMapping("M_MEDIADATA")); + Object returnObject = method.invoke(item); + HashMap data = new HashMap(); + if (returnObject != null) { + + + Class returnClass = returnObject.getClass(); + + Field fieldB = returnClass.getDeclaredField(getMapping("F_VID")); + fieldB.setAccessible(true); + String mediaUrl = (String) fieldB.get(returnObject); + + Field fieldC = returnClass.getDeclaredField(getMapping("F_VID_CDC")); + fieldC.setAccessible(true); + String c = (String) fieldC.get(returnObject); + String ext = getExtension(c); + + data.put("type",strRes("drafts_empty_video")); + data.put("ext",ext); + data.put("url",mediaUrl); + mediaData.add(data); + + } else { + Field fieldY = itemClass.getDeclaredField(getMapping("F_IMG")); + fieldY.setAccessible(true); + String mediaUrl = (String) fieldY.get(item); + String ext = "jpg"; + data.put("type",strRes("drafts_empty_photo")); + data.put("ext",ext); + data.put("url",mediaUrl+"?name=4096x4096&format=jpg"); + mediaData.add(data); + + } + } catch (Exception e) { + logger(e.toString()); + } + } + + } catch (Exception e) { + e.printStackTrace(); + } + } + + return mediaData; + } + + public static void downloader(Context ctx,Object t57){ + try { + + HashMap cachedData = new HashMap<>(); + String fileName = ""; + String username = ""; + ArrayList mediaData = new ArrayList<>(); + + Class clazz = t57.getClass(); + clazz.cast(t57); + if(isMapNotPresent("M_GETID")){ + setMapping("M_GETID","getId"); + } + //get id + Method getIdMethod = clazz.getDeclaredMethod(getMapping("M_GETID")); + Long idLong = (Long) getIdMethod.invoke(t57); + String postId = String.valueOf(idLong); + + if(isCached(postId)){ + cachedData = getCache(postId); + username = (String)cachedData.get("username"); + fileName = getFilename(postId,username); + mediaData = (ArrayList)cachedData.get("mediaData"); + }else { + + if (isMapNotPresent("M_USERNAME") || isMapNotPresent("M_MEDIAOBJ")) { + Method[] methods = clazz.getDeclaredMethods(); + int c = 0, ind = 0; + for (Method method : methods) { + if (method.getReturnType().equals(String.class) && c < 5) { + c++; + if (c == 3) { + setMapping("M_USERNAME", method.getName()); + } else if (c == 4) { + setMapping("M_MEDIAOBJ", methods[ind + 1].getName()); + break; + } + continue; + } + + ind++; + } + } + + + //get username + Method userNameMethod = clazz.getDeclaredMethod(getMapping("M_USERNAME")); + username = (String) userNameMethod.invoke(t57); + + fileName = getFilename(postId,username); + + Method yghMethod = clazz.getDeclaredMethod(getMapping("M_MEDIAOBJ")); + Object yghObj = (Object) yghMethod.invoke(t57); + + + mediaData = getMedia(yghObj); + cachedData.put("username",username); + cachedData.put("mediaData",mediaData); + setCache(postId,cachedData); + } + + + if(mediaData.size()==0){ + Utils.toast(strRes("piko_pref_native_downloader_no_media")); + return; + } + alertbox(ctx,fileName,mediaData); + }catch (Exception ex){ + logger(ex.toString()); + } + } + + + + private static void alertbox(Context ctx,String filename,ArrayList mediaData) throws NoSuchFieldException, IllegalAccessException { + LinearLayout ln = new LinearLayout(ctx); + ln.setOrientation(LinearLayout.VERTICAL); + AlertDialog.Builder builder = new AlertDialog.Builder(ctx); + builder.setTitle(strRes("piko_pref_native_downloader_alert_title")); + + int n = mediaData.size(); + String[] choices = new String[n]; + for(int i=0;i hashMap = mediaData.get(i); + String typ = (String)hashMap.get("type"); + choices[i] = "• "+typ+" "+String.valueOf(i+1); + } + + builder.setItems(choices, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int which) { + ArrayList mData = new ArrayList(); + HashMap media = mediaData.get(which); + mData.add(media); + downloadMedia(filename,mData); + dialogInterface.dismiss(); + } + }); + builder.setNegativeButton(strRes("piko_pref_native_downloader_download_all"), + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialogInterface, int index) { + downloadMedia(filename,mediaData); + dialogInterface.dismiss(); + } + }); + + builder.show(); + + //endfunc + } + + private static void downloadMedia(String filename,ArrayList mediaData){ + try{ + Utils.toast(strRes("download_started")); + int n = mediaData.size(); + for(int i=0;i media = mediaData.get(i); + String mediaUrl = (String)media.get("url"); + String ext = (String)media.get("ext"); + + String updFileName = filename+"_"+String.valueOf(i+1)+"."+ext; + + Utils.downloadFile(mediaUrl,updFileName); + } + + } catch (Exception e){ + Utils.logger(e.toString()); + } + } + + private static String strRes(String tag) { + return Utils.strRes(tag); + } + + private static void logger(String tag) { + Utils.logger(tag); + } + + + //end class +} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/integrations/twitter/settings/SettingsAboutFragment.java b/app/src/main/java/app/revanced/integrations/twitter/settings/SettingsAboutFragment.java index eb3c742a80..3737b957cd 100644 --- a/app/src/main/java/app/revanced/integrations/twitter/settings/SettingsAboutFragment.java +++ b/app/src/main/java/app/revanced/integrations/twitter/settings/SettingsAboutFragment.java @@ -101,6 +101,8 @@ public void onCreate(@org.jetbrains.annotations.Nullable Bundle savedInstanceSta flags.put(strRemoveRes("piko_pref_hide_premium_prompt"),SettingsStatus.hidePremiumPrompt); flags.put(strRemoveRes("piko_pref_hide_hidden_replies"),SettingsStatus.hideHiddenReplies); flags.put(strRes("piko_pref_del_from_db"),SettingsStatus.deleteFromDb); + flags.put(strRes("piko_pref_video_download"),SettingsStatus.enableVidDownload); + flags.put(strRes("piko_title_native_downloader"),SettingsStatus.nativeDownloader); LegacyTwitterPreferenceCategory patPref = preferenceCategory(strRes("piko_pref_patches"), screen); diff --git a/app/src/main/java/app/revanced/integrations/twitter/settings/SettingsFragment.java b/app/src/main/java/app/revanced/integrations/twitter/settings/SettingsFragment.java index 88225700b7..f900e92f06 100644 --- a/app/src/main/java/app/revanced/integrations/twitter/settings/SettingsFragment.java +++ b/app/src/main/java/app/revanced/integrations/twitter/settings/SettingsFragment.java @@ -76,7 +76,7 @@ public void onCreate(@Nullable Bundle savedInstanceState) { //download section if (SettingsStatus.enableDownloadSection()) { LegacyTwitterPreferenceCategory downloadPrefs = preferenceCategory(strRes("piko_title_download"), screen); - if (SettingsStatus.changeDownloadEnabled) { + if (SettingsStatus.changeDownloadEnabled || SettingsStatus.nativeDownloader) { downloadPrefs.addPreference(listPreference( strRes("piko_pref_download_path"), strRes("piko_pref_download_path_desc"), @@ -718,6 +718,9 @@ private static void setBooleanPerf(String key, Boolean val) { app.revanced.integrations.twitter.Utils.setBooleanPerf(key, val); } + private static String getStringPref(StringSetting setting) { + return app.revanced.integrations.twitter.Utils.getStringPref(setting); + } private static void setStringPref(String key, String val) { app.revanced.integrations.twitter.Utils.setStringPref(key, val); } diff --git a/app/src/main/java/app/revanced/integrations/twitter/settings/SettingsStatus.java b/app/src/main/java/app/revanced/integrations/twitter/settings/SettingsStatus.java index 39ac11cb67..13a091e8a0 100644 --- a/app/src/main/java/app/revanced/integrations/twitter/settings/SettingsStatus.java +++ b/app/src/main/java/app/revanced/integrations/twitter/settings/SettingsStatus.java @@ -52,7 +52,9 @@ public class SettingsStatus { public static boolean cleartrackingparams = false; public static boolean unshortenlink = false; public static boolean deleteFromDb = false; + public static boolean nativeDownloader = false; + public static void nativeDownloader() { nativeDownloader = true; } public static void deleteFromDb() { deleteFromDb = true; } public static void cleartrackingparams() { cleartrackingparams = true; } public static void unshortenlink() { unshortenlink = true; } @@ -109,7 +111,7 @@ public class SettingsStatus { public static boolean enableTimelineSection(){ return (inlineBarCustomisation || navBarCustomisation || disableAutoTimelineScroll || forceTranslate || hidePromoteButton || hideCommunityNote|| hideLiveThreads || hideBanner || hideInlineBmk || showPollResultsEnabled || hideImmersivePlayer); } public static boolean enableMiscSection() { return (roundOffNumbers || enableFontMod || hideRecommendedUsers || hideFAB || hideViewCount || customSharingDomainEnabled || hideFABBtns); } public static boolean enableAdsSection() {return (hideAds|| hideGAds || hideWTF || hideCTS || hideCTJ || hideDetailedPosts || hideRBMK ||hidePromotedTrend); } - public static boolean enableDownloadSection() {return (changeDownloadEnabled || mediaLinkHandle); } + public static boolean enableDownloadSection() {return (nativeDownloader || changeDownloadEnabled || mediaLinkHandle); } public static boolean enablePremiumSection() {return (enableReaderMode || enableUndoPosts || customAppIcon); } public static boolean enableCustomisationSection() {return (navBarCustomisation || sideBarCustomisation || profileTabCustomisation || timelineTabCustomisation); }