From 9b44ce5966baf374d085094a2b6591d01f26581a Mon Sep 17 00:00:00 2001 From: "mudhayarajan.2013@gmail.com" Date: Tue, 1 Mar 2022 20:18:41 +0530 Subject: [PATCH] Update to version 3.2.0 --- .idea/deploymentTargetDropDown.xml | 11 +- app/build.gradle | 32 +- app/src/main/AndroidManifest.xml | 49 +- .../mugames/vidsnap/extractor/Extractor.java | 72 +- .../mugames/vidsnap/extractor/Facebook.java | 398 +++++---- .../mugames/vidsnap/extractor/Instagram.java | 271 +++--- .../mugames/vidsnap/extractor/Periscope.java | 7 +- .../com/mugames/vidsnap/extractor/Twitch.java | 6 +- .../mugames/vidsnap/extractor/Twitter.java | 34 +- .../mugames/vidsnap/extractor/YouTube.java | 311 ++++--- .../main/java/com/mugames/vidsnap/m3u8.java | 164 ++-- .../mugames/vidsnap/network/Downloader.java | 22 +- .../mugames/vidsnap/network/HttpRequest.java | 113 ++- .../com/mugames/vidsnap/storage/FileUtil.java | 48 +- .../vidsnap/ui/activities/MainActivity.java | 104 ++- .../ui/fragments/DownloadFragment.java | 7 +- .../vidsnap/ui/fragments/HistoryFragment.java | 8 +- .../vidsnap/ui/fragments/QualityFragment.java | 153 +++- .../vidsnap/ui/fragments/StatusFragment.java | 11 +- .../vidsnap/ui/fragments/VideoFragment.java | 266 ++---- .../ui/viewmodels/MainActivityViewModel.java | 40 +- .../viewmodels/StatusFragmentViewModel.java | 10 +- .../ui/viewmodels/VideoFragmentViewModel.java | 78 +- .../vidsnap/utility/DownloadReceiver.java | 53 +- .../vidsnap/utility/JSInterpreter.java | 823 ++++++++++++++---- .../vidsnap/utility/JSInterpreterLegacy.java | 340 ++++++++ .../mugames/vidsnap/utility/UtilityClass.java | 64 +- .../vidsnap/utility/UtilityInterface.java | 24 +- .../vidsnap/utility/VideoSharedBroadcast.java | 60 ++ .../utility/bundles/DownloadDetails.java | 2 +- 30 files changed, 2430 insertions(+), 1151 deletions(-) create mode 100644 app/src/main/java/com/mugames/vidsnap/utility/JSInterpreterLegacy.java create mode 100644 app/src/main/java/com/mugames/vidsnap/utility/VideoSharedBroadcast.java diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml index 0f220b4..0695fa8 100644 --- a/.idea/deploymentTargetDropDown.xml +++ b/.idea/deploymentTargetDropDown.xml @@ -1,16 +1,17 @@ - + - + - - + + - + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 31952e8..fde8706 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,8 +12,8 @@ android { applicationId "com.mugames.vidsnap" minSdkVersion 21 targetSdkVersion 31 - versionCode 8 - versionName "3.1.2(beta)" + versionCode 9 + versionName "3.2.0" multiDexEnabled true @@ -62,21 +62,21 @@ dependencies { implementation 'com.google.firebase:firebase-crashlytics' implementation 'com.google.firebase:firebase-analytics' - implementation 'androidx.appcompat:appcompat:1.3.1' - implementation "androidx.activity:activity:1.3.1" - implementation "androidx.fragment:fragment:1.3.6" - implementation 'com.google.android.material:material:1.4.0' - implementation 'androidx.constraintlayout:constraintlayout:2.1.1' - implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1' - implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1' + implementation 'androidx.appcompat:appcompat:1.4.1' + implementation "androidx.activity:activity:1.4.0" + implementation "androidx.fragment:fragment:1.4.1" + implementation 'com.google.android.material:material:1.5.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.0' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation files('libs/java-json.jar') - implementation 'androidx.preference:preference:1.1.1' + implementation 'androidx.preference:preference:1.2.0' implementation 'com.google.firebase:firebase-config:21.0.1' - implementation 'com.google.firebase:firebase-database:20.0.2' + implementation 'com.google.firebase:firebase-database:20.0.3' //Fetch implementation "androidx.tonyodev.fetch2okhttp:xfetch2okhttp:3.1.6" @@ -88,7 +88,7 @@ dependencies { //Glide implementation 'com.github.bumptech.glide:glide:4.12.0' - implementation 'androidx.browser:browser:1.3.0' + implementation 'androidx.browser:browser:1.4.0' annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0' @@ -96,13 +96,13 @@ dependencies { androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' - implementation 'androidx.appcompat:appcompat:1.3.1' + implementation 'androidx.appcompat:appcompat:1.4.1' //Room - def room_version = "2.3.0" + def room_version = "2.4.1" implementation "androidx.room:room-runtime:$room_version" annotationProcessor "androidx.room:room-compiler:$room_version" // optional - RxJava2 support for Room @@ -114,4 +114,8 @@ dependencies { // optional - Test helpers testImplementation "androidx.room:room-testing:$room_version" + //LeakCanary + debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8.1' + + } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b8aef4c..a114695 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -12,45 +12,54 @@ - - - + - + android:theme="@style/Theme.YouTubeDownloader.NoActionBar" /> + + + + + - + android:theme="@style/Theme.YouTubeDownloader.NoActionBar"> @@ -58,9 +67,9 @@ - + - + miniExecutes = new ArrayList<>(); for (int i = 0; i < formats.audioURLs.size(); i++) { - if(formats.audioURLs.get(i)==null) { + if (formats.audioURLs.get(i) == null) { formats.audioSizes.add(0L); countDownLatch.countDown(); continue; @@ -270,7 +271,7 @@ private void getSizeForAudio() { int index = miniExecute.getBundle().getInt(EXTRA_INDEX); long size = miniExecute.getSize(); formats.audioSizes.set(index, size); - formats.audioURLs.set(index,filterURLs(formats.audioURLs.get(index))); + formats.audioURLs.set(index, filterURLs(formats.audioURLs.get(index))); } isAudioSizeReady = true; checkForCompletion(); @@ -294,10 +295,15 @@ public int getCookiesKey() { return -1; } - public void trySignIn(String notificationTxt, String url, String[] validDoneURLS, UtilityInterface.LoginIdentifier loginIdentifier) { + public void trySignIn(String notificationTxt, String url, String[] validDoneURLS, + UtilityInterface.LoginIdentifier loginIdentifier) { String cookies = loginHelper.getCookies(getCookiesKey()); if (cookies == null) - loginHelper.signInNeeded(notificationTxt, url, validDoneURLS, getCookiesKey(), loginIdentifier); + loginHelper.signInNeeded( + new UtilityClass.LoginDetailsProvider( + notificationTxt, url, validDoneURLS, getCookiesKey(), loginIdentifier + ) + ); else loginIdentifier.loggedIn(cookies); } @@ -307,7 +313,7 @@ private void checkForManifestCompletion() { if (isManifestReady) completed(); } - private void checkForCompletion() { + private synchronized void checkForCompletion() { if (isAudioSizeReady && isVideoSizeReady) completed(); } @@ -335,24 +341,30 @@ public void whatsAppStatusAnalyzed() { @Override public void onAnalyzeCompleted(Formats formats) { wrappedDialogueInterface.dismiss(); - new Handler(applicationContext.getMainLooper()).post(()-> analyzeCallback.onAnalyzeCompleted(formats)); + new Handler(applicationContext.getMainLooper()).post(() -> analyzeCallback.onAnalyzeCompleted(formats)); } }; DialogueInterface wrappedDialogueInterface = new DialogueInterface() { @Override public void show(String text) { - new Handler(applicationContext.getMainLooper()).post(()-> dialogueInterface.show(text)); + new Handler(applicationContext.getMainLooper()).post(() -> { + if (dialogueInterface != null) + dialogueInterface.show(text); + }); } @Override public void error(String message, Exception e) { - new Handler(applicationContext.getMainLooper()).post(()-> dialogueInterface.error(message,e)); + new Handler(applicationContext.getMainLooper()).post(() -> { + if (dialogueInterface != null) + dialogueInterface.error(message, e); + }); } @Override public void dismiss() { - new Handler(applicationContext.getMainLooper()).post(()-> dialogueInterface.dismiss()); + new Handler(applicationContext.getMainLooper()).post(() -> dialogueInterface.dismiss()); } }; @@ -360,31 +372,33 @@ public void setLink(String url) { this.url = url; } - boolean isNetworkAvailable(){ + boolean isNetworkAvailable() { ConnectivityManager connectivityManager = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { - Network network = connectivityManager.getActiveNetwork(); - if(network==null) return false; + Network network = connectivityManager.getActiveNetwork(); + if (network == null) return false; NetworkCapabilities networkCapabilities = connectivityManager.getNetworkCapabilities(network); - if(networkCapabilities==null) return false; - if(networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) return true; - if(networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) return true; + if (networkCapabilities == null) return false; + if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) return true; + if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) + return true; return networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET); - }else { + } else { NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo(); int type = networkInfo.getType(); - switch (type){ + switch (type) { case TYPE_WIFI: case TYPE_ETHERNET: case TYPE_MOBILE: return true; - default: return false; + default: + return false; } } } - String filterURLs(String url){ + String filterURLs(String url) { return url.replaceAll("\\\\", ""); } } diff --git a/app/src/main/java/com/mugames/vidsnap/extractor/Facebook.java b/app/src/main/java/com/mugames/vidsnap/extractor/Facebook.java index 3a59ca7..55c0352 100644 --- a/app/src/main/java/com/mugames/vidsnap/extractor/Facebook.java +++ b/app/src/main/java/com/mugames/vidsnap/extractor/Facebook.java @@ -39,8 +39,8 @@ public class Facebook extends Extractor { - static final int SUCCESS=-1;//Null if fails - String TAG= Statics.TAG+":Facebook"; + static final int SUCCESS = -1;//Null if fails + String TAG = Statics.TAG + ":Facebook"; String ID; String url; @@ -50,13 +50,12 @@ public class Facebook extends Extractor { boolean tried_with_forceEng; + String userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.122 Safari/537.36"; - String userAgent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.122 Safari/537.36"; + static String PAGELET_REGEX = "(?:pagelet_group_mall|permalink_video_pagelet|hyperfeed_story_id_[0-9a-f]+)"; - static String PAGELET_REGEX="(?:pagelet_group_mall|permalink_video_pagelet|hyperfeed_story_id_[0-9a-f]+)"; - - Hashtable headers; + Hashtable headers; public Facebook() { @@ -64,40 +63,52 @@ public Facebook() { } - String getId(String url){ - Pattern pattern=Pattern.compile("(?:https?://(?:[\\w-]+\\.)?(?:facebook\\.com|facebookcorewwwi\\.onion)/(?:[^#]*?#!/)?(?:(?:video/video\\.php|photo\\.php|video\\.php|video/embed|story\\.php|watch(?:/live)?/?)\\?(?:.*?)(?:v|video_id|story_fbid)=|[^/]+/videos/(?:[^/]+/)?|[^/]+/posts/|groups/[^/]+/permalink/|watchparty/)|facebook:)([0-9]+)"); - Matcher matcher=pattern.matcher(url); + String getId(String url) { + Pattern pattern = Pattern.compile("(?:https?://(?:[\\w-]+\\.)?(?:facebook\\.com|facebookcorewwwi\\.onion)/(?:[^#]*?#!/)?(?:(?:video/video\\.php|photo\\.php|video\\.php|video/embed|story\\.php|watch(?:/live)?/?)\\?(?:.*?)(?:v|video_id|story_fbid)=|[^/]+/videos/(?:[^/]+/)?|[^/]+/posts/|groups/[^/]+/permalink/|watchparty/)|facebook:)([0-9]+)"); + Matcher matcher = pattern.matcher(url); if (matcher.find()) return matcher.group(1); return null; } @Override public void analyze(String url) { - ID= getId(url); - headers=new Hashtable<>(); + ID = getId(url); + headers = new Hashtable<>(); - headers.put("Accept","text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"); - headers.put("User-Agent",userAgent); + headers.put("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"); + headers.put("User-Agent", userAgent); - if(url.startsWith("facebook:")) url=String.format("https://www.facebook.com/video/video.php?v=%s",ID); - this.url=url; + if (url.startsWith("facebook:")) + url = String.format("https://www.facebook.com/video/video.php?v=%s", ID); + this.url = url; getDialogueInterface().show("Accessing Server..."); extractInfo(); } private void extractInfo() { - url = url.replaceAll("://m.facebook\\.com/","://www.facebook.com/"); - HttpRequest request = new HttpRequest(url,getDialogueInterface(),response -> scratchWebPage(response.getResponse())); + url = url.replaceAll("://m.facebook\\.com/", "://www.facebook.com/"); + HttpRequest request = new HttpRequest(url, response -> { + if (response.getException() != null) { + getDialogueInterface().error(response.getResponse(), response.getException()); + return; + } + scratchWebPage(response.getResponse()); + }); request.setType(HttpRequest.GET); request.setHeaders(headers); request.start(); } - void extractForceEng(){ - url = url.replaceAll("://m.facebook\\.com/","://www.facebook.com/"); - url = url.replaceAll("://www.facebook\\.com/","://en-gb.facebook.com/"); - HttpRequest request = new HttpRequest(url,getDialogueInterface(),response -> { + void extractForceEng() { + url = url.replaceAll("://m.facebook\\.com/", "://www.facebook.com/"); + url = url.replaceAll("://www.facebook\\.com/", "://en-gb.facebook.com/"); + headers.put("Accept-Language", "en-GB,en-US,en"); + HttpRequest request = new HttpRequest(url, response -> { tried_with_forceEng = true; + if (response.getException() != null) { + getDialogueInterface().error(response.getResponse(), response.getException()); + return; + } scratchWebPage(response.getResponse()); }); request.setType(HttpRequest.GET); @@ -138,56 +149,53 @@ private void scratchWebPage(String webPage) { video_data = grabRelayPrefetchedDataSearchUrl(webPage); } - if(video_data == null){ - matcher=Pattern.compile("class=\"[^\"]*uiInterstitialContent[^\"]*\">
(.*?)
").matcher(webPage); - if(matcher.find()){ - getDialogueInterface().error("This video unavailable. FB says : "+matcher.group(1),null); + if (video_data == null) { + matcher = Pattern.compile("class=\"[^\"]*uiInterstitialContent[^\"]*\">
(.*?)
").matcher(webPage); + if (matcher.find()) { + getDialogueInterface().error("This video unavailable. FB says : " + matcher.group(1), null); return; } - if(webPage.contains("You must log in to continue")) - if(!tried_with_cookies) { + if (webPage.contains("You must log in to continue")) + if (!tried_with_cookies) { tryWithCookies(); return; } } - if(video_data==null){ - if(!tried_with_forceEng) + if (video_data == null) { + if (!tried_with_forceEng) extractForceEng(); - else getDialogueInterface().error("This video can't be Downloaded",null); - } - - - else { + else getDialogueInterface().error("This video can't be Downloaded", null); + } else { Matcher m; - if(formats.thumbNailsURL.isEmpty()){ - m=Pattern.compile("\"thumbnailImage\":\\{\"uri\":\"(.*?)\"\\}").matcher(webPage); - if(m.find()) formats.thumbNailsURL.add(m.group(1)); + if (formats.thumbNailsURL.isEmpty()) { + m = Pattern.compile("\"thumbnailImage\":\\{\"uri\":\"(.*?)\"\\}").matcher(webPage); + if (m.find()) formats.thumbNailsURL.add(m.group(1)); else { - m=Pattern.compile("\"thumbnailUrl\":\"(.*?)\"").matcher(webPage); - if(m.find()) formats.thumbNailsURL.add(m.group(1)); + m = Pattern.compile("\"thumbnailUrl\":\"(.*?)\"").matcher(webPage); + if (m.find()) formats.thumbNailsURL.add(m.group(1)); } } - if(formats.title==null || formats.title.equals("null")){ - m= Pattern.compile("(?:true|false),\"name\":\"(.*?)\",\"savable").matcher(webPage); - if(m.find()) formats.title=m.group(1); + if (formats.title == null || formats.title.equals("null")) { + m = Pattern.compile("(?:true|false),\"name\":\"(.*?)\",\"savable").matcher(webPage); + if (m.find()) formats.title = m.group(1); else { - m=Pattern.compile("<[Tt]itle id=\"pageTitle\">(.*?) \\| Facebook<\\/title>").matcher(webPage); - if(m.find()) formats.title= decodeHTML(m.group(1)); - else{ - m=Pattern.compile("title\" content=\"(.*?)\"").matcher(webPage); - if(m.find()) formats.title= decodeHTML(m.group(1)); + m = Pattern.compile("<[Tt]itle id=\"pageTitle\">(.*?) \\| Facebook<\\/title>").matcher(webPage); + if (m.find()) formats.title = decodeHTML(m.group(1)); + else { + m = Pattern.compile("title\" content=\"(.*?)\"").matcher(webPage); + if (m.find()) formats.title = decodeHTML(m.group(1)); } } - if(formats.title==null || formats.title.equals("null")) - formats.title="Facebook_Video"; + if (formats.title == null || formats.title.equals("null")) + formats.title = "Facebook_Video"; } UpdateUI(); } - }catch (JSONException e) { + } catch (JSONException e) { e.printStackTrace(); - getDialogueInterface().error("Something went wrong",e); + getDialogueInterface().error("Something went wrong", e); } } @@ -197,8 +205,8 @@ private void UpdateUI() { } - void tryWithCookies(){ - if(getUserCookies() ==null) { + void tryWithCookies() { + if (getUserCookies() == null) { trySignIn("Facebook requested you to sign-in. Without sign-in video can't be downloaded", "https://www.facebook.com/login/", new String[]{"https://m.facebook.com/login/save-device/?login_source=login#_=_", "https://m.facebook.com/?_rdr", @@ -213,108 +221,110 @@ void tryWithCookies(){ return; } - tried_with_cookies=true; + tried_with_cookies = true; getDialogueInterface().show("Adding cookies..."); - HttpRequest request = new HttpRequest(url,getDialogueInterface(), response -> scratchWebPage(response.getResponse())); + HttpRequest request = new HttpRequest(url, response -> { + if (response.getException() != null) { + getDialogueInterface().error(response.getResponse(), response.getException()); + return; + } + scratchWebPage(response.getResponse()); + }); request.setCookies(getUserCookies()); request.setType(HttpRequest.GET); request.setHeaders(headers); request.start(); } - Object grabRelayPrefetchedDataSearchUrl(String webpage){ - try { - JSONObject data = grabRelayPrefetchedData(webpage,new String[]{"\"dash_manifest\"","\"playable_url\""}); - if(data!=null) { - JSONArray nodes = null; - try { - nodes = data.getJSONArray("nodes"); - } catch (JSONException e) { - } - JSONObject node = UtilityClass.JSONGetter.getObj_or_Null(data, "node"); - if (nodes == null && node != null) { - nodes = new JSONArray(); - nodes.put(data); - } + Object grabRelayPrefetchedDataSearchUrl(String webpage) throws JSONException { + JSONObject data = grabRelayPrefetchedData(webpage, new String[]{"\"dash_manifest\"", "\"playable_url\""}); + if (data != null) { + JSONArray nodes = null; + try { + nodes = data.getJSONArray("nodes"); + } catch (JSONException e) { + } + JSONObject node = UtilityClass.JSONGetter.getObj_or_Null(data, "node"); + if (nodes == null && node != null) { + nodes = new JSONArray(); + nodes.put(data); + } - if (nodes != null) { - for (int j = 0; j < nodes.length(); j++) { - node = nodes.getJSONObject(j).getJSONObject("node"); - JSONArray attachments = null; - JSONObject story = node.getJSONObject("comet_sections").getJSONObject("content").getJSONObject("story"); - try { - attachments = story.getJSONObject("attached_story").getJSONArray("attachments"); - } catch (JSONException e) { - attachments = story.getJSONArray("attachments"); - } + if (nodes != null) { + for (int j = 0; j < nodes.length(); j++) { + node = nodes.getJSONObject(j).getJSONObject("node"); + JSONArray attachments = null; + JSONObject story = node.getJSONObject("comet_sections").getJSONObject("content").getJSONObject("story"); + try { + attachments = story.getJSONObject("attached_story").getJSONArray("attachments"); + } catch (JSONException e) { + attachments = story.getJSONArray("attachments"); + } - for (int k = 0; k < attachments.length(); k++) { - try { - //attachments.getJSONObject(k).getJSONObject("style_type_renderer").getJSONObject("attachment"); - JSONObject attachment = UtilityClass.JSONGetter.getObj_or_Null(UtilityClass.JSONGetter.getObj_or_Null(getObj_or_Null(attachments, k), "style_type_renderer"), "attachment"); - JSONArray ns = getArray_or_Null(UtilityClass.JSONGetter.getObj_or_Null(attachment, "all_subattachments"), "nodes"); - if (ns != null) { - for (int l = 0; l < ns.length(); l++) { - parseAttachment(ns.getJSONObject(l), "media"); - } - } - parseAttachment(attachment, "media"); - } catch (JSONException e) { + for (int k = 0; k < attachments.length(); k++) { + //attachments.getJSONObject(k).getJSONObject("style_type_renderer").getJSONObject("attachment"); + JSONObject attachment = UtilityClass.JSONGetter.getObj_or_Null(UtilityClass.JSONGetter.getObj_or_Null(getObj_or_Null(attachments, k), "style_type_renderer"), "attachment"); + JSONArray ns = getArray_or_Null(UtilityClass.JSONGetter.getObj_or_Null(attachment, "all_subattachments"), "nodes"); + if (ns != null) { + for (int l = 0; l < ns.length(); l++) { + parseAttachment(ns.getJSONObject(l), "media"); } } - + parseAttachment(attachment, "media"); } + } + } - JSONArray edges = getArray_or_Null(UtilityClass.JSONGetter.getObj_or_Null(UtilityClass.JSONGetter.getObj_or_Null(data, "mediaset"), "currMedia"), "edges"); - if (edges != null) { - for (int j = 0; j < edges.length(); j++) { - JSONObject edge = edges.getJSONObject(j); - parseAttachment(edge, "node"); - } + JSONArray edges = getArray_or_Null(UtilityClass.JSONGetter.getObj_or_Null(UtilityClass.JSONGetter.getObj_or_Null(data, "mediaset"), "currMedia"), "edges"); + if (edges != null) { + for (int j = 0; j < edges.length(); j++) { + JSONObject edge = edges.getJSONObject(j); + parseAttachment(edge, "node"); } + } - JSONObject video = UtilityClass.JSONGetter.getObj_or_Null(data, "video"); - if (video != null) { - JSONArray attachments = null; - try { - attachments = video.getJSONObject("story").getJSONArray("attachments"); - } catch (JSONException e) { - attachments = getArray_or_Null(UtilityClass.JSONGetter.getObj_or_Null(video, "creation_story"), "attachments"); - } - if (attachments != null) { - for (int j = 0; j < attachments.length(); j++) { - parseAttachment(attachments.getJSONObject(j), "media"); - } + JSONObject video = UtilityClass.JSONGetter.getObj_or_Null(data, "video"); + if (video != null) { + JSONArray attachments = null; + try { + attachments = video.getJSONObject("story").getJSONArray("attachments"); + } catch (JSONException e) { + attachments = getArray_or_Null(UtilityClass.JSONGetter.getObj_or_Null(video, "creation_story"), "attachments"); + } + if (attachments != null) { + for (int j = 0; j < attachments.length(); j++) { + parseAttachment(attachments.getJSONObject(j), "media"); } - if (formats.mainFileURLs.isEmpty()) parse_graphql_video(video); } - - if (!formats.mainFileURLs.isEmpty()) return SUCCESS; + if (formats.mainFileURLs.isEmpty()) parse_graphql_video(video); } - } catch (JSONException e) {} + if (!formats.mainFileURLs.isEmpty()) return SUCCESS; + } + return null; } - - String grabRelayData(String webPage, String[] searchWords){ + + String grabRelayData(String webPage, String[] searchWords) { Matcher m = Pattern.compile("handleWithCustomApplyEach\\(.*?,(.*)\\);").matcher(webPage); - while (m.find()){ - Matcher m1=Pattern.compile("(\\{.*[^);]\\})\\);").matcher(Objects.requireNonNull(m.group(1))); - if(m1.find()) - for (String s:searchWords) - if(m1.group(1).contains(s)) + while (m.find()) { + Matcher m1 = Pattern.compile("(\\{.*[^);]\\})\\);").matcher(Objects.requireNonNull(m.group(1))); + if (m1.find()) + for (String s : searchWords) + if (m1.group(1).contains(s)) return m1.group(1); } return null; } - JSONObject grabRelayPrefetchedData(String webPage, String[] filter){ - String jsonString= grabRelayData(webPage,filter); - if(isNonNullOrEmpty(jsonString)) { + JSONObject grabRelayPrefetchedData(String webPage, String[] filter) { + String jsonString = grabRelayData(webPage, filter); + if (isNonNullOrEmpty(jsonString)) { try { - JSONArray require = new JSONObject(jsonString).getJSONArray("require"); - for (int i=0; i < require.length(); i++) { + JSONObject jsonObj = new JSONObject(jsonString); + JSONArray require = jsonObj.getJSONArray("require"); + for (int i = 0; i < require.length(); i++) { JSONArray array = require.getJSONArray(i); if (array.getString(0).equals("RelayPrefetchedStreamCache")) return array.getJSONArray(3).getJSONObject(1).getJSONObject("__bbox").getJSONObject("result").getJSONObject("data"); @@ -326,83 +336,87 @@ JSONObject grabRelayPrefetchedData(String webPage, String[] filter){ return null; } - void parseAttachment(JSONObject attachment, String key){ - - try { - JSONObject media= UtilityClass.JSONGetter.getObj_or_Null(attachment,key); - if(media!=null && media.getString("__typename").equals("Video")){ - parse_graphql_video(media); - } - }catch (JSONException e){} + void parseAttachment(JSONObject attachment, String key) throws JSONException { + JSONObject media = UtilityClass.JSONGetter.getObj_or_Null(attachment, key); + if (media != null && media.getString("__typename").equals("Video")) { + parse_graphql_video(media); + } } - void parse_graphql_video(JSONObject media){ + void parse_graphql_video(JSONObject media) throws JSONException { + String res; try { - String res; formats.thumbNailsURL.add(media.getJSONObject("thumbnailImage").getString("uri")); + } catch (JSONException e) { + formats.thumbNailsURL.add(media.getJSONObject("preferred_thumbnail").getJSONObject("image").getString("uri")); + } - String title= getString_or_Null(media,"name"); - if(title==null) title= getString_or_Null(UtilityClass.JSONGetter.getObj_or_Null(media,"savable_description"),"text"); - formats.title=title; + String title = getString_or_Null(media, "name"); + if (title == null) + title = getString_or_Null(UtilityClass.JSONGetter.getObj_or_Null(media, "savable_description"), "text"); + formats.title = title; - String dash_xml = getString_or_Null(media, "dash_manifest"); - if (dash_xml != null) { - extractFromDash(dash_xml); - } + String dash_xml = getString_or_Null(media, "dash_manifest"); + if (dash_xml != null) { + extractFromDash(dash_xml); + } + try { res = media.get("width") + "x" + media.get("height"); - for (String suffix : new String[]{"", "_quality_hd"}) { - String playable_url = getString_or_Null(media, "playable_url" + suffix); - - if (playable_url == null || playable_url.equals("null")) continue; - formats.mainFileURLs.add(playable_url); - formats.fileMime.add(MIMEType.VIDEO_MP4); - formats.audioURLs.add(null); - formats.audioMime.add(null); - - if (suffix.equals("")) - formats.qualities.add(res + "(" + "SD" + ")"); - if (suffix.equals("_quality_hd")) - formats.qualities.add(res + "(" + "HD" + ")"); - } - }catch (JSONException e){} + } catch (JSONException e) { + res = media.get("original_width") + "x" + media.get("original_height"); + } + for (String suffix : new String[]{"", "_quality_hd"}) { + String playable_url = getString_or_Null(media, "playable_url" + suffix); + + if (playable_url == null || playable_url.equals("null")) continue; + formats.mainFileURLs.add(playable_url); + formats.fileMime.add(MIMEType.VIDEO_MP4); + formats.audioURLs.add(null); + formats.audioMime.add(null); + + if (suffix.equals("")) + formats.qualities.add(res + "(" + "SD" + ")"); + if (suffix.equals("_quality_hd")) + formats.qualities.add(res + "(" + "HD" + ")"); + } } - void extractFromDash(String xml){ - xml=xml.replaceAll("x3C","<"); - xml=xml.replaceAll("\\\\\u003C","<"); + void extractFromDash(String xml) { + xml = xml.replaceAll("x3C", "<"); + xml = xml.replaceAll("\\\\\u003C", "<"); - try{ + try { JSONArray AdaptionSet = XML.toJSONObject(xml).getJSONObject("MPD").getJSONObject("Period").getJSONArray("AdaptationSet"); JSONArray videos = AdaptionSet.getJSONObject(0).getJSONArray("Representation"); JSONObject audios = AdaptionSet.getJSONObject(1); String audio_url; String audio_mime; - String res=null; - String pre=""; + String res = null; + String pre = ""; JSONObject audio_rep; - try{ + try { audio_rep = audios.getJSONObject("Representation"); - }catch (JSONException e){ - audio_rep=audios.getJSONArray("Representation").getJSONObject(0); + } catch (JSONException e) { + audio_rep = audios.getJSONArray("Representation").getJSONObject(0); } audio_url = audio_rep.getString("BaseURL"); - audio_mime = getString_or_Null(audio_rep,"_mimeType"); - if(audio_mime==null) audio_mime = audio_rep.getString("mimeType"); - else pre="_"; + audio_mime = getString_or_Null(audio_rep, "_mimeType"); + if (audio_mime == null) audio_mime = audio_rep.getString("mimeType"); + else pre = "_"; for (int i = 0; i < videos.length(); i++) { JSONObject video = videos.getJSONObject(i); String video_url = video.getString("BaseURL"); try { - res = video.getString(pre+"FBQualityLabel") + "(" + video.getString(pre+"FBQualityClass").toUpperCase() + ")"; + res = video.getString(pre + "FBQualityLabel") + "(" + video.getString(pre + "FBQualityClass").toUpperCase() + ")"; } catch (JSONException e) { - res = video.get(pre+"width") + "x" + video.get(pre+"height"); + res = video.get(pre + "width") + "x" + video.get(pre + "height"); } - String video_mime=video.getString(pre+"mimeType"); + String video_mime = video.getString(pre + "mimeType"); formats.fileMime.add(video_mime); formats.mainFileURLs.add(video_url); @@ -416,35 +430,36 @@ void extractFromDash(String xml){ } - Object grabFromJSmodsInstance(JSONObject js_data){ - if(isNonNullOrEmpty(String.valueOf(js_data))) { + Object grabFromJSmodsInstance(JSONObject js_data) { + if (isNonNullOrEmpty(String.valueOf(js_data))) { try { return grab_video_data(js_data.getJSONObject("jsmods").getJSONArray("instances")); - } catch (JSONException e) {} + } catch (JSONException e) { + } } return null; } Object grab_video_data(JSONArray instance) { - try{ - for(int i=0;i { + HttpRequest request = new HttpRequest(url, response -> { getDialogueInterface().show("Analysing"); - extractInfoShared(response.getResponse()); + if (response.getException() != null){ + getDialogueInterface().error(response.getResponse(),response.getException()); + return; + } + try { + extractInfoShared(response.getResponse()); + } catch (JSONException e) { + getDialogueInterface().error("Internal Error", e); + } }); request.setType(HttpRequest.GET); request.start(); @@ -74,122 +81,119 @@ public void onReceivedCookies(String cookies) { } - private void extractInfoShared(String page) { + private void extractInfoShared(String page) throws JSONException { String jsonString; - if(page==null) { + if (page == null) { tryWithCookies(); return; } - try { - Pattern pattern = Pattern.compile("window\\._sharedData\\s*=\\s*(\\{.+?\\});"); - Matcher matcher = pattern.matcher(page); - if (matcher.find()) { - jsonString = matcher.group(1); - } else { - tryWithCookies(); + Pattern pattern = Pattern.compile("window\\._sharedData\\s*=\\s*(\\{.+?\\});"); + Matcher matcher = pattern.matcher(page); + if (matcher.find()) { + jsonString = matcher.group(1); + } else { + tryWithCookies(); + return; + } + JSONObject media; + JSONObject jsonObject = new JSONObject(String.valueOf(jsonString)); + JSONArray postPage = UtilityClass.JSONGetter.getArray_or_Null( + UtilityClass.JSONGetter.getObj_or_Null(jsonObject, "entry_data"), + "PostPage"); + + if (postPage != null) { + JSONObject zero = UtilityClass.JSONGetter.getObj_or_Null(postPage, 0); + JSONObject graphql = UtilityClass.JSONGetter.getObj_or_Null(zero, "graphql"); + + if (graphql != null) + media = UtilityClass.JSONGetter.getObj_or_Null(graphql, "shortcode_media"); + else media = UtilityClass.JSONGetter.getObj_or_Null(zero, "media"); + if (media == null) { + getDialogueInterface().show("Attempting different URL"); + extractInfoAdd(page); return; } - JSONObject media; - JSONObject jsonObject = new JSONObject(String.valueOf(jsonString)); - JSONArray postPage = UtilityClass.JSONGetter.getArray_or_Null( - UtilityClass.JSONGetter.getObj_or_Null(jsonObject, "entry_data"), - "PostPage"); - - if (postPage != null) { - JSONObject zero = UtilityClass.JSONGetter.getObj_or_Null(postPage, 0); - JSONObject graphql = UtilityClass.JSONGetter.getObj_or_Null(zero, "graphql"); - - if (graphql != null) - media = UtilityClass.JSONGetter.getObj_or_Null(graphql, "shortcode_media"); - else media = UtilityClass.JSONGetter.getObj_or_Null(zero, "media"); - if (media == null) { - getDialogueInterface().show("Attempting different URL"); - ExtractInfoAdd(page); - return; - } - setInfo(media); - } else { - ExtractInfoAdd(page); - } - - } catch (JSONException e) { - e.printStackTrace(); - getDialogueInterface().error("Internal Error Occurred Please try again.",e); + setInfo(media); + } else { + extractInfoAdd(page); } } - void RequestWithCookies(String cookies) { + void requestWithCookies(String cookies) { if (cookies == null || cookies.isEmpty()) { -// activity.dialog.dismiss(); trySignIn("Instagram.com says you to login. To download it you need to login Instagram.com", "https://www.instagram.com/accounts/login/", new String[]{"https://www.instagram.com/"}, cookies1 -> { getDialogueInterface().show("Adding Cookies"); -// activity.setStringValue(R.string.key_instagram, cookies); - RequestWithCookies(cookies1); + requestWithCookies(cookies1); }); return; } - HttpRequest request = new HttpRequest(httpURL,getDialogueInterface(),response -> extractInfoShared(response.getResponse())); + HttpRequest request = new HttpRequest(httpURL, response -> { + if (response.getException() != null){ + getDialogueInterface().error(response.getResponse(),response.getException()); + return; + } + try { + extractInfoShared(response.getResponse()); + } catch (JSONException e) { + getDialogueInterface().error("Internal Error", e); + } + }); request.setCookies(cookies); request.setType(HttpRequest.GET); request.start(); } - void setInfo(JSONObject media) { - try { - - String videoName = getString_or_Null(media, "title"); - //media.getJSONObject("edge_media_to_caption").getJSONArray("edges").getJSONObject(0).getJSONObject("node").getString("text") - - if (videoName == null || videoName.equals("null") || videoName.isEmpty()) - videoName = getString_or_Null( - UtilityClass.JSONGetter.getObj_or_Null( - getObj_or_Null( - getArray_or_Null( - UtilityClass.JSONGetter.getObj_or_Null( - media, "edge_media_to_caption") - , "edges") - , 0), "node") - , "text"); - - if (videoName == null || videoName.equals("null") || videoName.isEmpty()) - videoName = "instagram_video"; - String fileURL = getString_or_Null(media, "video_url"); - if (fileURL == null) { - JSONArray edges = getArray_or_Null(UtilityClass.JSONGetter.getObj_or_Null(media, "edge_sidecar_to_children"), "edges"); - if (edges == null) { - getDialogueInterface().error("This media can't be downloaded",new Exception("IDK try it" + media)); - return; - } - for (int i = 0; i < edges.length(); i++) { - JSONObject node = edges.getJSONObject(i).getJSONObject("node"); - if (node.getBoolean("is_video")) { - formats.thumbNailsURL.add(nodeToThumb(node)); - formats.mainFileURLs.add(nodeToVideo(node)); - formats.qualities.add("--"); - formats.fileMime.add(MIMEType.VIDEO_MP4); - } + void setInfo(JSONObject media) throws JSONException { + + String videoName = getString_or_Null(media, "title"); + //media.getJSONObject("edge_media_to_caption").getJSONArray("edges").getJSONObject(0).getJSONObject("node").getString("text") + + if (videoName == null || videoName.equals("null") || videoName.isEmpty()) + videoName = getString_or_Null( + UtilityClass.JSONGetter.getObj_or_Null( + getObj_or_Null( + getArray_or_Null( + UtilityClass.JSONGetter.getObj_or_Null( + media, "edge_media_to_caption") + , "edges") + , 0), "node") + , "text"); + + if (videoName == null || videoName.equals("null") || videoName.isEmpty()) + videoName = "instagram_video"; + String fileURL = getString_or_Null(media, "video_url"); + if (fileURL == null) { + JSONArray edges = getArray_or_Null(UtilityClass.JSONGetter.getObj_or_Null(media, "edge_sidecar_to_children"), "edges"); + if (edges == null) { + getDialogueInterface().error("This media can't be downloaded", new Exception("IDK try it" + media)); + return; + } + for (int i = 0; i < edges.length(); i++) { + JSONObject node = edges.getJSONObject(i).getJSONObject("node"); + if (node.getBoolean("is_video")) { + formats.thumbNailsURL.add(nodeToThumb(node)); + formats.mainFileURLs.add(nodeToVideo(node)); + formats.qualities.add("--"); + formats.fileMime.add(MIMEType.VIDEO_MP4); } - } else{ - formats.mainFileURLs.add(fileURL); - formats.fileMime.add(MIMEType.VIDEO_MP4); - formats.qualities.add("--"); - formats.thumbNailsURL.add(media.getString("thumbnail_src")); } - videoName = videoName.replaceAll("\n", ""); - videoName = videoName.replaceAll("\\.", ""); + } else { + formats.mainFileURLs.add(fileURL); + formats.fileMime.add(MIMEType.VIDEO_MP4); + formats.qualities.add("--"); + formats.thumbNailsURL.add(media.getString("thumbnail_src")); + } + videoName = videoName.replaceAll("\n", ""); + videoName = videoName.replaceAll("\\.", ""); - formats.title = videoName; + formats.title = videoName; - updateVideoSize(); - } catch (JSONException e) { - getDialogueInterface().error("Internal Error Occurred",e); - e.printStackTrace(); - } + updateVideoSize(); } @@ -201,7 +205,6 @@ private String nodeToVideo(JSONObject node) throws JSONException { return node.getString("video_url"); } - int got = 0; //short media code => edge_sidecar_to_children => edges[] => o->n {} void updateVideoSize() { @@ -210,43 +213,75 @@ void updateVideoSize() { } - - void ExtractInfoAdd(String page) { + void extractInfoAdd(String page) throws JSONException { String jsonString = ""; - try { - Pattern pattern = Pattern.compile("window\\.__additionalDataLoaded\\s*\\(\\s*[^,]+,\\s*(\\{.+?\\})\\s*\\)\\s*;"); - Matcher matcher = pattern.matcher(page); - if (matcher.find()) { - - jsonString = matcher.group(1); - } - if (jsonString == null || jsonString.isEmpty()) { - tryWithCookies(); - return; - } - JSONObject media; - JSONObject jsonObject = new JSONObject(jsonString); - - JSONObject graphql = UtilityClass.JSONGetter.getObj_or_Null(jsonObject, "graphql"); + Pattern pattern = Pattern.compile("window\\.__additionalDataLoaded\\s*\\(\\s*[^,]+,\\s*(\\{.+?\\})\\s*\\)\\s*;"); + Matcher matcher = pattern.matcher(page); + if (matcher.find()) { + jsonString = matcher.group(1); + } + if (jsonString == null || jsonString.isEmpty()) { + tryWithCookies(); + return; + } + JSONObject media; + JSONObject jsonObject = new JSONObject(jsonString); + JSONObject graphql = UtilityClass.JSONGetter.getObj_or_Null(jsonObject, "graphql"); + if (graphql != null) { + //Old instagram json response has media + //But new one has items media = UtilityClass.JSONGetter.getObj_or_Null(graphql, "shortcode_media"); if (media == null) { - getDialogueInterface().error("Something went wrong",new Exception("doesn't find media")); + getDialogueInterface().error("Something went wrong", new Exception("doesn't find media")); return; } setInfo(media); - } catch (JSONException e) { - e.printStackTrace(); + } else { + // With new instagram response adds more video quality option + //This else part helps to extract video from new response + JSONArray items = jsonObject.getJSONArray("items"); + extractFromItems(items); } } + private void extractFromItems(JSONArray items) throws JSONException { + for (int i = 0; i < items.length(); i++) { + JSONArray videoVersion = items.getJSONObject(i).getJSONArray("video_versions"); + for (int j = 0; j < videoVersion.length(); j++) { + JSONObject video = videoVersion.getJSONObject(j); + formats.mainFileURLs.add(video.getString("url")); + formats.qualities.add(video.getString("width") + "x" + video.getString("height")); + formats.fileMime.add(MIMEType.VIDEO_MP4); + } + + JSONObject imageVersion2 = items.getJSONObject(i).getJSONObject("image_versions2"); + JSONArray candidates = imageVersion2.getJSONArray("candidates"); + formats.thumbNailsURL.add(candidates.getJSONObject(0).getString("url")); + JSONObject caption = getObj_or_Null(items.getJSONObject(i),"caption"); + String title; + if (caption!=null){ + title = caption.getString("text"); + }else{ + try{ + title = items.getJSONObject(i).getString("caption"); + if (title.equals("null")) title = "Instagram_Reels"; + }catch (JSONException e){ + title = "Instagram reels"; + } + } + formats.title = title; + } + updateVideoSize(); + } + private void tryWithCookies() { - if(!own_used && !own_cookie.isEmpty()) { - RequestWithCookies(own_cookie); - own_used=true; - }else { - RequestWithCookies(getUserCookies()); + if (!own_used && !own_cookie.isEmpty()) { + requestWithCookies(own_cookie); + own_used = true; + } else { + requestWithCookies(getUserCookies()); } } diff --git a/app/src/main/java/com/mugames/vidsnap/extractor/Periscope.java b/app/src/main/java/com/mugames/vidsnap/extractor/Periscope.java index 04bc251..c97ee39 100644 --- a/app/src/main/java/com/mugames/vidsnap/extractor/Periscope.java +++ b/app/src/main/java/com/mugames/vidsnap/extractor/Periscope.java @@ -62,8 +62,11 @@ public void extractInfo(String url) { extractor.getDialogueInterface().show("Periscope video"); manifest = new ArrayList>(); - HttpRequest request = new HttpRequest(String.format("https://api.periscope.tv/api/v2/accessVideoPublic?broadcast_id=%s", id), - extractor.getDialogueInterface(),response -> { + HttpRequest request = new HttpRequest(String.format("https://api.periscope.tv/api/v2/accessVideoPublic?broadcast_id=%s", id),response -> { + if (response.getException() != null){ + extractor.getDialogueInterface().error(response.getResponse(),response.getException()); + return; + } try { JSONObject stream = new JSONObject(response.getResponse()); JSONObject broadcast = stream.getJSONObject("broadcast"); diff --git a/app/src/main/java/com/mugames/vidsnap/extractor/Twitch.java b/app/src/main/java/com/mugames/vidsnap/extractor/Twitch.java index 783035d..c3a7b7d 100644 --- a/app/src/main/java/com/mugames/vidsnap/extractor/Twitch.java +++ b/app/src/main/java/com/mugames/vidsnap/extractor/Twitch.java @@ -68,7 +68,11 @@ public void extractURLFromClips(String twitchURL, TwitchInfoInterface i){ headers.put("Content-Type","text/plain;charset=UTF-8"); headers.put("Client-ID","kimne78kx3ncx6brgo4mv6wki5h1ko"); String data=String.format("{\"query\":\"{clip(slug:\\\"%s\\\"){broadcaster{displayName}createdAt curator{displayName id}durationSeconds id tiny:thumbnailURL(width:86,height:45)small:thumbnailURL(width:260,height:147)medium:thumbnailURL(width:480,height:272)title videoQualities{frameRate quality sourceURL}viewCount}}\"}",id); - HttpRequest request = new HttpRequest("https://gql.twitch.tv/gql",dialogueInterface,response -> { + HttpRequest request = new HttpRequest("https://gql.twitch.tv/gql",response -> { + if (response.getException() != null){ + dialogueInterface.error(response.getResponse(),response.getException()); + return; + } try { JSONObject clip=new JSONObject(response.getResponse()).getJSONObject("data").getJSONObject("clip"); formats.thumbNailsURL.add(clip.getString("medium")); diff --git a/app/src/main/java/com/mugames/vidsnap/extractor/Twitter.java b/app/src/main/java/com/mugames/vidsnap/extractor/Twitter.java index 67df3b2..0a74acd 100644 --- a/app/src/main/java/com/mugames/vidsnap/extractor/Twitter.java +++ b/app/src/main/java/com/mugames/vidsnap/extractor/Twitter.java @@ -88,7 +88,11 @@ String getTweetID(String url) { private void getToken() { getDialogueInterface().show("Getting Token"); - HttpRequest request = new HttpRequest(base_url+"guest/activate.json",getDialogueInterface(),response -> { + HttpRequest request = new HttpRequest(base_url+"guest/activate.json",response -> { + if (response.getException() != null){ + getDialogueInterface().error(response.getResponse(),response.getException()); + return; + } try { Log.d(TAG, "onReceive: " + new JSONObject(response.getResponse()).getString("guest_token")); headers.put("x-guest-token", new JSONObject(response.getResponse()).getString("guest_token")); @@ -105,7 +109,13 @@ private void getToken() { } void extractVideo() { - HttpRequest request = new HttpRequest(base_url+"statuses/show/"+tweetID+".json"+query,getDialogueInterface(),response -> identifyDownloader(response.getResponse())); + HttpRequest request = new HttpRequest(base_url+"statuses/show/"+tweetID+".json"+query,response ->{ + if (response.getException() != null){ + getDialogueInterface().error(response.getResponse(),response.getException()); + return; + } + identifyDownloader(response.getResponse()); + }); request.setType(HttpRequest.GET); request.setHeaders(headers); request.start(); @@ -180,7 +190,11 @@ private void identifyDownloader(String response){ private void fromVMap(String url) { - HttpRequest request = new HttpRequest(url,getDialogueInterface(),res -> { + HttpRequest request = new HttpRequest(url,res -> { + if (res.getException() != null){ + getDialogueInterface().error(res.getResponse(),res.getException()); + return; + } String response = res.getResponse(); Pattern pattern = Pattern.compile("(?<=)[\\s\\S]*(?=)"); Matcher matcher = pattern.matcher(response); @@ -259,7 +273,13 @@ String resolution(String url) { } void extractBroadcasts() { - HttpRequest request = new HttpRequest(String.format(base_url + "broadcasts/show.json?ids=%s", tweetID),getDialogueInterface(),response -> parseBroadcastResponse(response.getResponse())); + HttpRequest request = new HttpRequest(String.format(base_url + "broadcasts/show.json?ids=%s", tweetID),response -> { + if (response.getException() != null){ + getDialogueInterface().error(response.getResponse(),response.getException()); + return; + } + parseBroadcastResponse(response.getResponse()); + }); request.setHeaders(headers); request.setType(HttpRequest.GET); request.start(); @@ -271,7 +291,11 @@ void parseBroadcastResponse(String response){ info = extractInfo(broadcasts); String mediaKey = broadcasts.getString("media_key"); - HttpRequest request = new HttpRequest(String.format(base_url + "live_video_stream/status/%s", mediaKey),getDialogueInterface(),res1 -> { + HttpRequest request = new HttpRequest(String.format(base_url + "live_video_stream/status/%s", mediaKey),res1 -> { + if (res1.getException() != null){ + getDialogueInterface().error(res1.getResponse(),res1.getException()); + return; + } String response1 = res1.getResponse(); Pattern pattern = Pattern.compile("\\{[\\s\\S]+\\}"); Matcher matcher = pattern.matcher(response1); diff --git a/app/src/main/java/com/mugames/vidsnap/extractor/YouTube.java b/app/src/main/java/com/mugames/vidsnap/extractor/YouTube.java index bb71471..e53debc 100644 --- a/app/src/main/java/com/mugames/vidsnap/extractor/YouTube.java +++ b/app/src/main/java/com/mugames/vidsnap/extractor/YouTube.java @@ -21,6 +21,7 @@ import com.mugames.vidsnap.network.HttpRequest; import com.mugames.vidsnap.utility.JSInterpreter; +import com.mugames.vidsnap.utility.JSInterpreterLegacy; import com.mugames.vidsnap.utility.MIMEType; import com.mugames.vidsnap.utility.Statics; import com.mugames.vidsnap.utility.UtilityClass; @@ -29,6 +30,7 @@ import org.json.JSONException; import org.json.JSONObject; +import java.util.Hashtable; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -39,17 +41,20 @@ public class YouTube extends Extractor { static final String get_video_Info = "https://www.youtube.com/get_video_info?html5=1&video_id="; static final String embed_Info = "https://www.youtube.com/embed/"; static final String YT_URL = "https://www.youtube.com"; + static String YT_INITIAL_BOUNDARY_RE = "(?:var\\s+meta|]+\\bsrc=(\"[^\"]+\")[^>]+\\bname=[\"\\']player_ias\\/base", "\"jsUrl\"\\s*:\\s*(\"[^\"]+\")", "\"assets\":.+?\"js\":\\s*(\"[^\"]+\")" }; - String[] FUNCTION_REGEX = new String[]{ + static String[] FUNCTION_REGEX = new String[]{ "\\b[cs]\\s*&&\\s*[adf]\\.set\\([^,]+\\s*,\\s*encodeURIComponent\\s*\\(\\s*([a-zA-Z0-9$]+)\\(", "\\b[a-zA-Z0-9]+\\s*&&\\s*[a-zA-Z0-9]+\\.set\\([^,]+\\s*,\\s*encodeURIComponent\\s*\\(\\s*([a-zA-Z0-9$]+)\\(", "(?:\\b|[^a-zA-Z0-9$])([a-zA-Z0-9$]{2})\\s*=\\s*function\\(\\s*a\\s*\\)\\s*\\{\\s*a\\s*=\\s*a\\.split\\(\\s*\"\"\\s*\\)", @@ -64,7 +69,14 @@ public class YouTube extends Extractor { "\\bc\\s*&&\\s*[a-zA-Z0-9]+\\.set\\([^,]+\\s*,\\s*\\([^)]*\\)\\s*\\(\\s*([a-zA-Z0-9$]+)\\(" }; - public String videoID; + static String[] PLAYER_INFO_RE = new String[]{ + "s/player/([a-zA-Z0-9_-]{8,})", + "/([a-zA-Z0-9_-]{8,})/player(?:_ias\\.vflset(?:/[a-zA-Z]{2,3}_[a-zA-Z]{2,3})?|-plasma-ias-(?:phone|tablet)-[a-z]{2}_[A-Z]{2}\\.vflset)/base\\.js$", + "\\b(vfl[a-zA-Z0-9_-]+)\\b.*?\\.js$" + }; + + + String videoID; static final String _URL_REGEX = ".+((?<=\\/).+)$"; @@ -73,6 +85,8 @@ public class YouTube extends Extractor { String jsFunctionName; String jsCode; + String jsNFunctionName; + int attempt = 0; @@ -80,7 +94,7 @@ public class YouTube extends Extractor { boolean isAudioDecrypted; - public YouTube(){ + public YouTube() { super("YouTube"); } @@ -92,7 +106,13 @@ public void analyze(String url) { getDialogueInterface().error("URL Seems to be wrong", null); return; } - HttpRequest request = new HttpRequest(get_video_Info + videoID, getDialogueInterface(), response -> videoInfo(UtilityClass.decodeHTML(response.getResponse()))); + HttpRequest request = new HttpRequest(get_video_Info + videoID, response -> { + try { + videoInfo(UtilityClass.decodeHTML(response.getResponse())); + } catch (JSONException e) { + getDialogueInterface().error("Try again", e); + } + }); request.setType(HttpRequest.GET); request.start(); @@ -123,7 +143,7 @@ void analyzeCompleted() { } - public void videoInfo(String info) { + void videoInfo(String info) throws JSONException { if (info == null) { fetchFromWebPage(); return; @@ -131,11 +151,12 @@ public void videoInfo(String info) { getDialogueInterface().show("Downloading Player"); if (jsFunctionName == null || jsFunctionName.isEmpty()) - fetchJSFile(new JSDownloaded() { - @Override - public void onDone() { - getDialogueInterface().show("Analysing data"); + fetchJSFile(() -> { + getDialogueInterface().show("Analysing data"); + try { extractInfo(info); + } catch (JSONException e) { + getDialogueInterface().error("Couldn't download", e); } }); else extractInfo(info); @@ -144,129 +165,139 @@ public void onDone() { private void fetchFromWebPage() { webScratch = true; - String YT_INITIAL_BOUNDARY_RE = "(?:var\\s+meta| { - if(response.getResponse()==null) { + + HttpRequest request = new HttpRequest("https://www.youtube.com/watch?v=" + videoID + "&bpctr=9999999999&has_verified=1", response -> { + if (response.getException() != null) { + getDialogueInterface().error(response.getResponse(), response.getException()); + return; + } + if (response.getResponse() == null) { getDialogueInterface().error("Some thing went wrong\nPlease try again", response.getException()); } Matcher matcher = Pattern.compile(String.format("%s\\s*%s", YT_INITIAL_PLAYER_RESPONSE_RE, YT_INITIAL_BOUNDARY_RE)).matcher(response.getResponse()); - if (matcher.find()) videoInfo(matcher.group(1)); + if (matcher.find()) { + try { + videoInfo(matcher.group(1)); + } catch (JSONException e) { + getDialogueInterface().error("Try again", e); + } + } }); request.setType(HttpRequest.GET); request.start(); } - void extractInfo(String decoded) { - - try { - JSONObject jsonObject = new JSONObject(UtilityClass.parseQueryString(decoded)); - JSONObject player_response = getObj_or_Null(jsonObject, "player_response"); - if (!webScratch && player_response == null) { - alternateURLs(attempt++); - return; - } else player_response = new JSONObject(decoded); - - JSONObject streamingData = getObj_or_Null(player_response, "streamingData"); - if (streamingData == null || streamingData.toString().equals("{}")) { - JSONObject playabilityStatus = getObj_or_Null(player_response, "playabilityStatus"); - if (playabilityStatus != null) { - String status = playabilityStatus.getString("status"); - if (status.equals("LOGIN_REQUIRED")) { - //playabilityStatus.getString("reason") - getDialogueInterface().error("Age restricted videos can't be downloaded", null); - return; - } else if (status.equals("UNPLAYABLE")) { - getDialogueInterface().error("Movies can't be downloaded", null); - return; - } - } else alternateURLs(attempt++); - return; - } - JSONArray formatsObj = getArray_or_Null(streamingData, "adaptiveFormats");//formats - + void extractInfo(String decoded) throws JSONException { + JSONObject jsonObject = new JSONObject(UtilityClass.parseQueryString(decoded)); + JSONObject player_response = getObj_or_Null(jsonObject, "player_response"); + if (!webScratch && player_response == null) { + alternateURLs(attempt++); + return; + } else player_response = new JSONObject(decoded); + + JSONObject streamingData = getObj_or_Null(player_response, "streamingData"); + if (streamingData == null || streamingData.toString().equals("{}")) { + JSONObject playabilityStatus = getObj_or_Null(player_response, "playabilityStatus"); + if (playabilityStatus != null) { + String status = playabilityStatus.getString("status"); + if (status.equals("LOGIN_REQUIRED")) { + //playabilityStatus.getString("reason") + getDialogueInterface().error("Age restricted videos can't be downloaded", null); + return; + } else if (status.equals("UNPLAYABLE")) { + getDialogueInterface().error("Movies can't be downloaded", null); + return; + } + } else alternateURLs(attempt++); + return; + } + JSONArray formatsObj = getArray_or_Null(streamingData, "adaptiveFormats");//formats - for (int i = 0; i < formatsObj.length(); i++) { - JSONObject selectedJSON = new JSONObject(String.valueOf(formatsObj.getJSONObject(i))); - String unsupported = getString_or_Null(selectedJSON, "type"); + for (int i = 0; i < formatsObj.length(); i++) { + JSONObject selectedJSON = new JSONObject(String.valueOf(formatsObj.getJSONObject(i))); - if (unsupported != null) continue; + String unsupported = getString_or_Null(selectedJSON, "type"); - String mime = selectedJSON.getString("mimeType").split(";")[0]; - String urlField = getString_or_Null(selectedJSON, "url"); + if (unsupported != null) continue; - final boolean isWebmAudio = mime.equals(MIMEType.AUDIO_WEBM); - final boolean isAudio = isWebmAudio || mime.equals(MIMEType.AUDIO_MP4); + String mime = selectedJSON.getString("mimeType").split(";")[0]; + String urlField = getString_or_Null(selectedJSON, "url"); + final boolean isWebmAudio = mime.equals(MIMEType.AUDIO_WEBM); + final boolean isAudio = isWebmAudio || mime.equals(MIMEType.AUDIO_MP4); - if (!isAudio) { - String qual = selectedJSON.getString("qualityLabel"); - if (formats.qualities.contains(qual)) - continue; - formats.qualities.add(qual); - } + if (!isAudio) { + String qual = selectedJSON.getString("qualityLabel"); + if (formats.qualities.contains(qual)) + continue; + formats.qualities.add(qual); + } - if (urlField == null) { - String signatureCipher = getString_or_Null(selectedJSON, "signatureCipher"); - String z = UtilityClass.parseForSig(UtilityClass.decodeHTML(signatureCipher)); - JSONObject signatureCipherObj = new JSONObject(z); - String sp = getString_or_Null(signatureCipherObj, "sp"); - String s = signatureCipherObj.getString("s"); - urlField = signatureCipherObj.getString("url"); + if (urlField == null) { + String signatureCipher = getString_or_Null(selectedJSON, "signatureCipher"); + String z = UtilityClass.parseForSig(UtilityClass.decodeHTML(signatureCipher)); + JSONObject signatureCipherObj = new JSONObject(z); + String sp = getString_or_Null(signatureCipherObj, "sp"); + String s = signatureCipherObj.getString("s"); + urlField = signatureCipherObj.getString("url"); - if (isWebmAudio) { - if (formats.audioURLs.size() == 0) { - formats.audioSP = sp; - formats.audioURLs.add(urlField); - formats.audioSIG = s; - formats.audioMime.add(mime); - } - } else { - formats.videoSPs.add(sp); - formats.mainFileURLs.add(urlField); - formats.videoSIGs.add(s); - formats.fileMime.add(mime); - } - } else if (isAudio) { - if (isWebmAudio && formats.audioURLs.isEmpty()) { + if (isWebmAudio) { + if (formats.audioURLs.size() == 0) { + formats.audioSP = sp; formats.audioURLs.add(urlField); + formats.audioSIG = s; formats.audioMime.add(mime); } } else { + formats.videoSPs.add(sp); formats.mainFileURLs.add(urlField); + formats.videoSIGs.add(s); formats.fileMime.add(mime); } + + } else if (isAudio) { + if (isWebmAudio && formats.audioURLs.isEmpty()) { + formats.audioURLs.add(urlField); + formats.audioMime.add(mime); + } + } else { + formats.mainFileURLs.add(urlField); + formats.fileMime.add(mime); } + } - JSONObject videoDetails = player_response.getJSONObject("videoDetails"); - formats.title = videoDetails.getString("title").replaceAll("[\\\\/:*?\"<>|]+", "_"); - JSONObject thumbnail = videoDetails.getJSONObject("thumbnail"); - JSONArray thumbnails = thumbnail.getJSONArray("thumbnails"); - JSONObject thumbIndex = getObj_or_Null(thumbnails, 3); - if (thumbIndex == null) - thumbIndex = getObj_or_Null(thumbnails, thumbnails.length() - 1); - formats.thumbNailsURL.add(thumbIndex.getString("url")); + JSONObject videoDetails = player_response.getJSONObject("videoDetails"); + formats.title = videoDetails.getString("title").replaceAll("[\\\\/:*?\"<>|]+", "_"); + JSONObject thumbnail = videoDetails.getJSONObject("thumbnail"); + JSONArray thumbnails = thumbnail.getJSONArray("thumbnails"); + JSONObject thumbIndex = getObj_or_Null(thumbnails, 3); + if (thumbIndex == null) + thumbIndex = getObj_or_Null(thumbnails, thumbnails.length() - 1); + formats.thumbNailsURL.add(thumbIndex.getString("url")); + + tryDecrypt(); - tryDecrypt(); - } catch (JSONException e) { - e.printStackTrace(); - getDialogueInterface().error("Sorry this video can't be downloaded", e); - } } void tryDecrypt() { +// Still in under development +// try { +// unThrottleFormats(); +// } catch (JSONException e) { +// e.printStackTrace(); +// } if (formats.audioSIG == null) { analyzeCompleted(); return; } - new Thread(() -> decryptSignature(0,formats.audioSIG, formats.audioURLs.get(0), (index, decryptedSign, url) -> { + new Thread(() -> decryptSignature(0, formats.audioSIG, formats.audioURLs.get(0), (index, decryptedSign, url) -> { if (formats.audioSP == null) url += ("&signature=" + decryptedSign); else url += ("&" + formats.audioSP + "=" + decryptedSign); Log.d(TAG, "Video_Info Au: \n" + url); @@ -282,31 +313,75 @@ void tryDecrypt() { String s = formats.videoSIGs.get(i); String sp = formats.videoSPs.get(i); - int finalI = i; - new Thread(() -> decryptSignature(finalI,s, urlRaw, (index, decryptedSign, url) -> { + new Thread(() -> decryptSignature(finalI, s, urlRaw, (index, decryptedSign, url) -> { got++; - - if (sp == null) url += "&signature=" + decryptedSign; else url += ("&" + sp + "=" + decryptedSign); + Log.d(TAG, "tryDecrypt: signature = "+decryptedSign); Log.d(TAG, "Video_Info: \n" + url); - - formats.mainFileURLs.set(index, url); - boolean isLast = got == formats.mainFileURLs.size(); - if (isLast) { got = 0; isVideoDecrypted = true; checkCompletion(); } - })).start(); } + } + String extractNFunction() throws JSONException { + String target = "([a-zA-Z0-9$]{3})(?:\\[(\\d+)\\])?"; + Matcher matcher = Pattern.compile( + String.format("\\.get\\(\"n\"\\)\\)&&\\(b=(%s)\\([a-zA-Z0-9]\\)", target)) + .matcher(jsCode); + if (matcher.find()) { + String nameIdx = matcher.group(1); + if (nameIdx != null) { + Matcher m1 = Pattern.compile(target).matcher(nameIdx); + if (m1.find()) { + String name = m1.group(1); + String idx = m1.group(2); + if (idx == null) return name; + else { + matcher = Pattern.compile(String.format("var %s\\s*=\\s*(\\[.+?\\]);", name)).matcher(jsCode); + if (matcher.find()) { + String json = matcher.group(1); + JSONArray jsonArray = new JSONArray(json); + return jsonArray.getString(Integer.parseInt(idx)); + } + } + } + } + } + throw new JSONException("Unable to find function name"); + } + + void unThrottleFormats() throws JSONException { + String funcCode = null; + for (String signedUrl : formats.mainFileURLs) { + Hashtable parsed = parseUrl(signedUrl); + if (jsNFunctionName == null) jsNFunctionName = extractNFunction(); + if (funcCode==null){ + JSInterpreter jsInterpreter = new JSInterpreter(jsCode, null); + JSInterpreter.JSFunctionCode functionCode = jsInterpreter.extractFunctionCode(jsNFunctionName); + JSInterface jsInterface = jsInterpreter.extractFunctionFromCode(functionCode); + Object o = jsInterface.resf(new Object[]{parsed.get("n")}); + Log.d(TAG, "unThrottleFormats: "+String.valueOf(o)); + } + } + } + + private Hashtable parseUrl(String signedUrl) { + String[] parsed = signedUrl.split("&"); + Hashtable hashtable = new Hashtable<>(); + for (String i : parsed) { + String[] temp = i.split("="); + hashtable.put(temp[0], temp[1]); + } + return hashtable; } private void checkCompletion() { @@ -332,7 +407,17 @@ void alternateURLs(int attempts) { getDialogueInterface().error("URL Seems to be wrong", null); return; } - HttpRequest request = new HttpRequest(dataURL[attempts], getDialogueInterface(), response -> videoInfo(UtilityClass.decodeHTML(response.getResponse()))); + HttpRequest request = new HttpRequest(dataURL[attempts], response -> { + if (response.getException() != null) { + getDialogueInterface().error(response.getResponse(), response.getException()); + return; + } + try { + videoInfo(UtilityClass.decodeHTML(response.getResponse())); + } catch (JSONException e) { + getDialogueInterface().error("Internal error", e); + } + }); request.setType(HttpRequest.GET); request.start(); } @@ -340,14 +425,22 @@ void alternateURLs(int attempts) { void fetchJSFile(JSDownloaded jsDownloaded) { ResponseCallBack responseCallBack = embedResponse -> { + if (embedResponse.getException() != null) { + getDialogueInterface().error(embedResponse.getResponse(), embedResponse.getException()); + return; + } for (String i : PLAYER_REGEXS) { Pattern pattern = Pattern.compile(i); Matcher matcher = pattern.matcher(embedResponse.getResponse()); if (matcher.find()) { String player_url = matcher.group(1); - player_url = player_url==null?"":player_url.replaceAll("\"", ""); + player_url = player_url == null ? "" : player_url.replaceAll("\"", ""); - HttpRequest request = new HttpRequest(YT_URL+player_url,getDialogueInterface(),response -> { + HttpRequest request = new HttpRequest(YT_URL + player_url, response -> { + if (response.getException() != null) { + getDialogueInterface().error(response.getResponse(), response.getException()); + return; + } for (String j : FUNCTION_REGEX) { Pattern funPattern = Pattern.compile(j); Matcher funMatcher = funPattern.matcher(response.getResponse()); @@ -366,14 +459,14 @@ void fetchJSFile(JSDownloaded jsDownloaded) { } }; - HttpRequest request = new HttpRequest(embed_Info+videoID,getDialogueInterface(),responseCallBack); + HttpRequest request = new HttpRequest(embed_Info + videoID, responseCallBack); request.setType(HttpRequest.GET); request.start(); } void decryptSignature(int index, String signature, String url, SignNotifier notifier) { - JSInterpreter jsInterpreter = new JSInterpreter(jsCode, null); - JSInterface jsInterface = jsInterpreter.Extract_Function(jsFunctionName); + JSInterpreterLegacy jsInterpreter = new JSInterpreterLegacy(jsCode, null); + JSInterface jsInterface = jsInterpreter.extractFunction(jsFunctionName); char[] o = (char[]) jsInterface.resf(new String[]{signature}); notifier.onDecrypted(index, UtilityClass.charArrayToSting(o), url); } diff --git a/app/src/main/java/com/mugames/vidsnap/m3u8.java b/app/src/main/java/com/mugames/vidsnap/m3u8.java index ae1eb1e..74b8ebd 100644 --- a/app/src/main/java/com/mugames/vidsnap/m3u8.java +++ b/app/src/main/java/com/mugames/vidsnap/m3u8.java @@ -33,7 +33,7 @@ public class m3u8 { - String TAG= Statics.TAG+":m3u8"; + String TAG = Statics.TAG + ":m3u8"; JSONObject info; @@ -46,18 +46,19 @@ public m3u8(Extractor extractor) { this.extractor = extractor; } - public void extract_m3u8(String url, JSONObject info){ + public void extract_m3u8(String url, JSONObject info) { try { - if(info.get("isLive").equals("true")){ - extractor.getDialogueInterface().error("Live video can't be downloaded",null); + if (info.get("isLive").equals("true")) { + extractor.getDialogueInterface().error("Live video can't be downloaded", null); return; } - }catch (JSONException e){} + } catch (JSONException e) { + } extractor.getDialogueInterface().show("This may take a while."); - this.info=info; + this.info = info; - real_extract(0,url, new ChunkCallback() { + real_extract(0, url, new ChunkCallback() { @Override public void onChunkExtracted(int index, ArrayList chunkURLS) { //Called only if it is not playlist else completingProcess() directly called from extractFromPlaylist @@ -69,24 +70,26 @@ public void onChunkExtracted(int index, ArrayList chunkURLS) { } - private void real_extract(int index, String url, ChunkCallback chunkCallback) { - HttpRequest request = new HttpRequest(url, extractor.getDialogueInterface(), response -> { - if(response.getResponse().contains("#EXT-X-FAXS-CM:")){ - extractor.getDialogueInterface().error("Adobe Flash access can't downloaded",null); + HttpRequest request = new HttpRequest(url, response -> { + if (response.getException() != null){ + extractor.getDialogueInterface().error(null,response.getException()); return; } - Pattern pattern=Pattern.compile("#EXT-X-SESSION-KEY:.*?URI=\"skd://"); - if(pattern.matcher(response.getResponse()).find()){ - extractor.getDialogueInterface().error("Apple Fair Play protected can't downloaded",null); + if (response.getResponse().contains("#EXT-X-FAXS-CM:")) { + extractor.getDialogueInterface().error("Adobe Flash access can't downloaded", null); + return; + } + Pattern pattern = Pattern.compile("#EXT-X-SESSION-KEY:.*?URI=\"skd://"); + if (pattern.matcher(response.getResponse()).find()) { + extractor.getDialogueInterface().error("Apple Fair Play protected can't downloaded", null); return; } - if(response.getResponse().contains("#EXT-X-TARGETDURATION")){ + if (response.getResponse().contains("#EXT-X-TARGETDURATION")) { //No playlist - extractFromMeta(index,response.getResponse(),url,chunkCallback); - } - else { - extractFromPlaylist(response.getResponse(),url); + extractFromMeta(index, response.getResponse(), url, chunkCallback); + } else { + extractFromPlaylist(response.getResponse(), url); } }); request.setType(HttpRequest.GET); @@ -95,15 +98,16 @@ private void real_extract(int index, String url, ChunkCallback chunkCallback) { } //size - int got=0; - void completingProcess(){ + int got = 0; + + void completingProcess() { try { - extractor.formats.title=info.getString("title"); - }catch (JSONException e){ - extractor.formats.title="Stream_video"; + extractor.formats.title = info.getString("title"); + } catch (JSONException e) { + extractor.formats.title = "Stream_video"; } - if(extractor.formats.manifest.size()==1) { + if (extractor.formats.manifest.size() == 1) { try { extractor.formats.qualities.add(info.getString("resolution")); } catch (JSONException e) { @@ -119,18 +123,17 @@ void completingProcess(){ } - void extractFromPlaylist(String response, String url){ - ArrayList frag_urls=new ArrayList<>(); - for(String line:response.split("\n")){ - line=line.trim(); - if(!line.startsWith("#")){ - frag_urls.add(joinURL(url,line)); - extractor.formats.chunkUrlList.add(frag_urls.get(frag_urls.size()-1)); - } - else if(line.contains("RESOLUTION")){ - for (String l:line.split(",")){ - l=l.trim(); - if(l.contains("RESOLUTION")) { + void extractFromPlaylist(String response, String url) { + ArrayList frag_urls = new ArrayList<>(); + for (String line : response.split("\n")) { + line = line.trim(); + if (!line.startsWith("#")) { + frag_urls.add(joinURL(url, line)); + extractor.formats.chunkUrlList.add(frag_urls.get(frag_urls.size() - 1)); + } else if (line.contains("RESOLUTION")) { + for (String l : line.split(",")) { + l = l.trim(); + if (l.contains("RESOLUTION")) { extractor.formats.qualities.add(getResolution(l)); } } @@ -138,16 +141,16 @@ else if(line.contains("RESOLUTION")){ } - for (int i=0;i chunkURLS) { - extractor.formats.manifest.set(index,chunkURLS); + extractor.formats.manifest.set(index, chunkURLS); extractor.formats.fileMime.add(MIMEType.VIDEO_MP4); got++; - if(got==frag_urls.size()){ - got=0; + if (got == frag_urls.size()) { + got = 0; completingProcess(); } } @@ -159,59 +162,55 @@ public void onChunkExtracted(int index, ArrayList chunkURLS) { private void extractFromMeta(int index, String meta, String url, ChunkCallback chunkCallback) { ArrayList chunksUrl = new ArrayList<>(); - int mediaFrag=0; - int ad_frag=0; - boolean ad_frag_next=false; + int mediaFrag = 0; + int ad_frag = 0; + boolean ad_frag_next = false; if (canDownload(meta)) { for (String line : meta.split("\n")) { - line=line.trim(); + line = line.trim(); if (nullOrEmpty(line)) continue; - if(line.startsWith("#")){ + if (line.startsWith("#")) { if (is_ad_fragment_start(line)) ad_frag_next = true; else if (is_ad_fragment_end(line)) ad_frag_next = false; continue; } - if(ad_frag_next) { + if (ad_frag_next) { ad_frag++; continue; } - mediaFrag+=1; + mediaFrag += 1; } - int i=0; - int media_sequence=0; + int i = 0; + int media_sequence = 0; int frag_index = 0; ad_frag_next = false; - for (String line : meta.split("\n")){ - line=line.trim(); - if(!nullOrEmpty(line)){ - if(!line.startsWith("#")){ - if(ad_frag_next) continue; - chunksUrl.add(find_url(url,line)); - } - else if (line.startsWith("#EXT-X-KEY")){ - - } - else if (line.startsWith("#EXT-X-MEDIA-SEQUENCE")){ - media_sequence= Integer.parseInt(line.substring(22)); - } - else if(line.startsWith("#EXT-X-BYTERANGE")){ - - } - else if (is_ad_fragment_start(line)) ad_frag_next=true; - else if(is_ad_fragment_end(line)) ad_frag_next=false; + for (String line : meta.split("\n")) { + line = line.trim(); + if (!nullOrEmpty(line)) { + if (!line.startsWith("#")) { + if (ad_frag_next) continue; + chunksUrl.add(find_url(url, line)); + } else if (line.startsWith("#EXT-X-KEY")) { + + } else if (line.startsWith("#EXT-X-MEDIA-SEQUENCE")) { + media_sequence = Integer.parseInt(line.substring(22)); + } else if (line.startsWith("#EXT-X-BYTERANGE")) { + + } else if (is_ad_fragment_start(line)) ad_frag_next = true; + else if (is_ad_fragment_end(line)) ad_frag_next = false; } } } - chunkCallback.onChunkExtracted(index,chunksUrl); + chunkCallback.onChunkExtracted(index, chunksUrl); } private boolean canDownload(String meta) { - for (String s:new String[]{"#EXT-X-KEY:METHOD=(?!NONE|AES-128)","#EXT-X-MAP:"}){ - Pattern pattern=Pattern.compile(s); - if(pattern.matcher(meta).find()){ + for (String s : new String[]{"#EXT-X-KEY:METHOD=(?!NONE|AES-128)", "#EXT-X-MAP:"}) { + Pattern pattern = Pattern.compile(s); + if (pattern.matcher(meta).find()) { return false; } } @@ -220,32 +219,31 @@ private boolean canDownload(String meta) { } - boolean is_ad_fragment_start(String metaLine){ + boolean is_ad_fragment_start(String metaLine) { return metaLine.startsWith("#ANVATO-SEGMENT-INFO") && metaLine.contains("type=ad") || metaLine.startsWith("#UPLYNK-SEGMENT") && metaLine.endsWith(",ad"); } - boolean is_ad_fragment_end(String metaLine){ + boolean is_ad_fragment_end(String metaLine) { return metaLine.startsWith("#ANVATO-SEGMENT-INFO") && metaLine.contains("type=master") || metaLine.startsWith("#UPLYNK-SEGMENT") && metaLine.endsWith(",segment"); } - boolean nullOrEmpty(String s){ + boolean nullOrEmpty(String s) { return s == null || s.isEmpty(); } - String find_url(String mainURL,String line){ - if(Pattern.compile("^https?://").matcher(line).find()) return line; - return joinURL(mainURL,line); + String find_url(String mainURL, String line) { + if (Pattern.compile("^https?://").matcher(line).find()) return line; + return joinURL(mainURL, line); } - - String getResolution(String query){ - return query.substring("RESOLUTION".length()+1); + String getResolution(String query) { + return query.substring("RESOLUTION".length() + 1); } - interface ChunkCallback{ + interface ChunkCallback { void onChunkExtracted(int index, ArrayList chunkURLS); } diff --git a/app/src/main/java/com/mugames/vidsnap/network/Downloader.java b/app/src/main/java/com/mugames/vidsnap/network/Downloader.java index ddf3850..d5a59fc 100644 --- a/app/src/main/java/com/mugames/vidsnap/network/Downloader.java +++ b/app/src/main/java/com/mugames/vidsnap/network/Downloader.java @@ -46,11 +46,13 @@ import android.content.Intent; import android.media.MediaPlayer; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.ResultReceiver; import android.util.Log; import android.webkit.MimeTypeMap; +import android.webkit.WebView; import android.widget.Toast; import androidx.annotation.NonNull; @@ -90,6 +92,7 @@ import java.nio.channels.FileChannel; import java.sql.Time; import java.util.ArrayList; +import java.util.Objects; import java.util.Random; import java.util.Timer; import java.util.TimerTask; @@ -106,6 +109,9 @@ public class Downloader extends Service { private static PendingIntent getActivityOpenerIntent(Context context) { Intent intent = new Intent(context, MainActivity.class); intent.putExtra(ACTIVE_DOWNLOAD, true); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + return PendingIntent.getActivity(context, 10, intent, PendingIntent.FLAG_IMMUTABLE); + } return PendingIntent.getActivity(context, 10, intent, 0); } @@ -216,8 +222,12 @@ private void init() { Intent cancelIntent = new Intent(context, CancelDownloadReceiver.class); cancelIntent.putExtra(ID_CANCEL_DOWNLOAD_DETAILS, details.id); - PendingIntent cancelPendingIntent = PendingIntent.getBroadcast(context, ran, cancelIntent, PendingIntent.FLAG_ONE_SHOT); - + PendingIntent cancelPendingIntent; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + cancelPendingIntent = PendingIntent.getActivity(context, 10, intent, PendingIntent.FLAG_IMMUTABLE); + }else{ + cancelPendingIntent = PendingIntent.getBroadcast(context, ran, cancelIntent, PendingIntent.FLAG_ONE_SHOT); + } builder = new NotificationCompat.Builder(context, NOTIFY_DOWNLOADING); builder.setPriority(NotificationCompat.PRIORITY_LOW) @@ -250,6 +260,7 @@ private void init() { public void cancelDownload() { isCanceled = true; fetch.remove(request.getId()); + fetch.cancel(request.getId()); new Thread(() -> FileUtil.deleteFile(TEMP_AUDIO_NAME, null)).start(); new Thread(() -> FileUtil.deleteFile(TEMP_VIDEO_NAME, null)).start(); if (ffmpegInstance != null) { @@ -384,6 +395,7 @@ void copyVideoToDestination(String localFile, String localMime) { bundle.putBoolean(IS_SHARE_ONLY_DOWNLOAD, true); bundle.putString(FILE_MIME, localMime); sendBundle(PROGRESS_DONE, bundle); + FileUtil.deleteFile(TEMP_AUDIO_NAME,null); return; } outputUri = FileUtil.pathToNewUri(context, details.pathUri, details.fileName, localMime); @@ -399,6 +411,9 @@ void copyVideoToDestination(String localFile, String localMime) { inChannel.transferTo(0, inChannel.size(), outChannel); inChannel.close(); outChannel.close(); + FileUtil.deleteFile(localFile,null); + FileUtil.deleteFile(TEMP_VIDEO_NAME,null); + FileUtil.deleteFile(TEMP_AUDIO_NAME,null); } catch (IOException e) { Log.e(TAG, "copyVideoToDestination: ", e); @@ -452,6 +467,9 @@ void download(String url, String outPath, int whatDownloading) { MutableExtras extra = new MutableExtras(); extra.putInt(PROGRESS, whatDownloading); request.setExtras(extra); + request.addHeader("User-Agent", "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B)" + + "AppleWebKit/535.19 (KHTML, like Gecko)" + + "Chrome/18.0.1025.133 Mobile Safari/535.19"); fetch.attachFetchObserversForDownload(request.getId(), this).enqueue(request, result -> { diff --git a/app/src/main/java/com/mugames/vidsnap/network/HttpRequest.java b/app/src/main/java/com/mugames/vidsnap/network/HttpRequest.java index e82808b..4fa38fb 100644 --- a/app/src/main/java/com/mugames/vidsnap/network/HttpRequest.java +++ b/app/src/main/java/com/mugames/vidsnap/network/HttpRequest.java @@ -23,11 +23,15 @@ import com.mugames.vidsnap.utility.Statics; import com.mugames.vidsnap.utility.UtilityInterface; +import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.io.OutputStream; +import java.net.HttpURLConnection; import java.net.MalformedURLException; +import java.net.ProtocolException; import java.net.URL; import java.util.Enumeration; import java.util.Hashtable; @@ -46,7 +50,6 @@ public class HttpRequest{ Hashtable headers=new Hashtable<>(); String type; String data; - UtilityInterface.DialogueInterface dialogueInterface; public HttpRequest(MainActivity activity, String videoURL, String cookies, Hashtable headers, String type, String data, String user_agent, UtilityInterface.ResponseCallBack callBack){ try { @@ -62,10 +65,9 @@ public HttpRequest(MainActivity activity, String videoURL, String cookies, Hasht } } - public HttpRequest(String webpageURL, UtilityInterface.DialogueInterface dialogueInterface, UtilityInterface.ResponseCallBack callBack){ + public HttpRequest(String webpageURL, UtilityInterface.ResponseCallBack callBack){ try { this.info_url = new URL(webpageURL); - this.dialogueInterface = dialogueInterface; this.callBack = callBack; } catch (MalformedURLException e) { e.printStackTrace(); @@ -92,41 +94,20 @@ public void start(){ new Thread(()->{ try { //Sending Request to sever - HttpsURLConnection httpsURLConnection=(HttpsURLConnection) info_url.openConnection(); + HttpsURLConnection httpsURLConnection= getConnection(info_url.toString()); - if(headers!=null){ - Enumeration keys=headers.keys(); - while (keys.hasMoreElements()){ - String s = keys.nextElement(); - httpsURLConnection.setRequestProperty(s,headers.get(s)); - } - } - - - httpsURLConnection.setRequestProperty("Accept-Language", "en-GB"); - httpsURLConnection.setRequestProperty("Content-Language", "en-GB"); - - if(type!=null && type.equals(POST)){ - httpsURLConnection.setDoInput(true); - httpsURLConnection.setDoOutput(true); - httpsURLConnection.setRequestMethod("POST"); - } - else { - httpsURLConnection.setRequestMethod("GET"); - } + // normally, 3xx is redirect - if(data!=null){ - OutputStream outputStream = httpsURLConnection.getOutputStream(); - outputStream.write(data.getBytes()); - outputStream.close(); + while (isRedirected(httpsURLConnection)){ + String newUrl = httpsURLConnection.getHeaderField("Location"); + httpsURLConnection.disconnect(); + Log.d(TAG, "start: "+newUrl); + httpsURLConnection = null; + httpsURLConnection = getConnection(newUrl); + httpsURLConnection.connect(); } - if(cookies!=null) - httpsURLConnection.setRequestProperty("Cookie",cookies); - - Log.d(TAG, "run: "+(httpsURLConnection.getRequestProperties()).get("User-Agent")); - InputStream stream = httpsURLConnection.getInputStream(); @@ -136,21 +117,75 @@ public void start(){ while ((length=stream.read(buffers))!=-1){ outputStream.write(buffers,0,length); } +// BufferedReader in = new BufferedReader( +// new InputStreamReader(httpsURLConnection.getInputStream())); +// String inputLine; +// StringBuilder html = new StringBuilder(); +// +// while ((inputLine = in.readLine()) != null) { +// html.append(inputLine); +// } +// in.close(); stream.close(); callBack.onReceive(new Response(outputStream.toString())); } catch (IOException e) { e.printStackTrace(); - if(info_url.toString().contains("youtube") || (info_url.toString().contains("instagram") && !headers.contains("User-Agent"))) - callBack.onReceive(new Response(e)); - else { - dialogueInterface.error("Internal error occurred",e); - } - + callBack.onReceive(new Response(e)); } }).start(); } + private HttpsURLConnection getConnection(String url) throws IOException { + + HttpsURLConnection httpsURLConnection = (HttpsURLConnection) new URL(url).openConnection(); + + if(headers!=null){ + Enumeration keys=headers.keys(); + while (keys.hasMoreElements()){ + String s = keys.nextElement(); + httpsURLConnection.setRequestProperty(s,headers.get(s)); + } + + } + + + httpsURLConnection.setRequestProperty("Accept-Language", "en-GB"); + httpsURLConnection.setRequestProperty("Content-Language", "en-GB"); + + if(type!=null && type.equals(POST)){ + httpsURLConnection.setDoInput(true); + httpsURLConnection.setDoOutput(true); + httpsURLConnection.setRequestMethod("POST"); + } + else { + httpsURLConnection.setRequestMethod("GET"); + httpsURLConnection.setInstanceFollowRedirects(false); + } + + if(data!=null){ + OutputStream outputStream = httpsURLConnection.getOutputStream(); + outputStream.write(data.getBytes()); + outputStream.close(); + } + + if(cookies!=null) + httpsURLConnection.setRequestProperty("Cookie",cookies); + + Log.d(TAG, "run: "+(httpsURLConnection.getRequestProperties()).get("User-Agent")); + + return httpsURLConnection; + } + + boolean isRedirected(HttpsURLConnection httpsURLConnection) throws IOException { + int status = httpsURLConnection.getResponseCode(); + if (status != HttpURLConnection.HTTP_OK) { + return status == HttpURLConnection.HTTP_MOVED_TEMP + || status == HttpURLConnection.HTTP_MOVED_PERM + || status == HttpURLConnection.HTTP_SEE_OTHER; + } + return false; + } } diff --git a/app/src/main/java/com/mugames/vidsnap/storage/FileUtil.java b/app/src/main/java/com/mugames/vidsnap/storage/FileUtil.java index 63ee557..996399b 100644 --- a/app/src/main/java/com/mugames/vidsnap/storage/FileUtil.java +++ b/app/src/main/java/com/mugames/vidsnap/storage/FileUtil.java @@ -213,7 +213,7 @@ private static String getVolumePostR(String volumeID, Context context) { * Can't be used to write. Only for string purpose */ public static String getExternalStoragePublicDirectory(Context context, @Nullable String type) { - type = type==null?"":type; + type = type == null ? "" : type; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { StorageManager manager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE); @@ -274,13 +274,18 @@ public static String removeStuffFromName(String name) { name = name.replaceAll("[@#~\\n\\t]", ""); if (name.length() > 45) { int i = 45; - while (unicode(name, i)) i++; - name = name.substring(0, i); + while (unicode(name, i) && i < name.length()) i++; + if (i == name.length() - 1 && unicode(name, name.length() + 1)) { + while (unicode(name, i)) i--; + } + if (i != 0) name = name.substring(0, i); + else name = "Placeholder Title"; } return name; } private static boolean unicode(String s, int i) { + if (s.length()<=i) return false; s = s.substring(0, i); char c = s.charAt(i - 1); return Character.UnicodeBlock.of(c) != Character.UnicodeBlock.BASIC_LATIN; @@ -290,14 +295,14 @@ public static String getValidFile(String path, String name, String extension) { int num = 1; if (path == null) { - return name +"."+ extension; + return name + "." + extension; } - path = path + name.replaceAll("[\\|\\\\\\?\\*<>\":\n]+", "_")+"." + extension; + path = path + name.replaceAll("[\\|\\\\\\?\\*<>\":\n]+", "_") + "." + extension; File file = new File(path); while (file.exists()) { - path = file.getParent() + "/" + name + "(" + (num++) + ")" +"."+ extension; + path = file.getParent() + "/" + name + "(" + (num++) + ")" + "." + extension; file = new File(path); } return path; @@ -437,16 +442,16 @@ static void copyFolder(Context context, File src, File dest) { } } - public static void moveFile(Context context,File src, File dest, FileStreamCallback fileStreamCallback) { + public static void moveFile(Context context, File src, File dest, FileStreamCallback fileStreamCallback) { if (src.isDirectory()) { - copyFolder(context,src, dest); + copyFolder(context, src, dest); return; } if (src.getName().endsWith(".muout") || src.getName().endsWith(".muvideo") || src.getName().endsWith(".muaudio")) return; - copyFile(context,Uri.fromFile(src), Uri.fromFile(src), fileStreamCallback); + copyFile(context, Uri.fromFile(src), Uri.fromFile(src), fileStreamCallback); deleteFile(src.getAbsolutePath(), null); if (fileStreamCallback != null) fileStreamCallback.onFileOperationDone(); } @@ -456,9 +461,9 @@ public static void copyFile(Context context, Uri src, Uri dest, FileStreamCallba try { FileChannel inChannel = ((FileInputStream) context.getContentResolver().openInputStream(src)).getChannel(); FileChannel outChannel; - try{ - outChannel = new FileOutputStream(context.getContentResolver().openFileDescriptor(dest,"w").getFileDescriptor()).getChannel(); - }catch (FileNotFoundException e){ + try { + outChannel = new FileOutputStream(context.getContentResolver().openFileDescriptor(dest, "w").getFileDescriptor()).getChannel(); + } catch (FileNotFoundException e) { outChannel = new FileOutputStream(dest.getPath()).getChannel(); } inChannel.transferTo(0, inChannel.size(), outChannel); @@ -471,26 +476,25 @@ public static void copyFile(Context context, Uri src, Uri dest, FileStreamCallba } /** - * - * @param context application context + * @param context application context * @param parentUri parent directory's uri - * @param fileName current filename without extensions - * @param mimeType mime type of current file + * @param fileName current filename without extensions + * @param mimeType mime type of current file * @return New uri for a file eg. if file exist name will be file(1) */ - public static Uri pathToNewUri(Context context, Uri parentUri, String fileName, String mimeType){ + public static Uri pathToNewUri(Context context, Uri parentUri, String fileName, String mimeType) { DocumentFile directory; try { directory = DocumentFile.fromTreeUri(context, parentUri); DocumentFile file = directory.createFile(mimeType, fileName); return file.getUri(); - } catch (IllegalArgumentException|NullPointerException e) { + } catch (IllegalArgumentException | NullPointerException e) { return Uri.fromFile(new File(FileUtil.getValidFile(parentUri.getPath() + File.separator, fileName, MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType)))); } } - public static synchronized void scanMedia(Context context, String fileUri, MediaScannerConnection.OnScanCompletedListener listener){ + public static synchronized void scanMedia(Context context, String fileUri, MediaScannerConnection.OnScanCompletedListener listener) { Uri uri = Uri.parse(fileUri); String path = FileUtil.uriToPath(context, uri); @@ -499,9 +503,9 @@ public static synchronized void scanMedia(Context context, String fileUri, Media MediaScannerConnection.scanFile(context, new String[]{path}, null, new MediaScannerConnection.OnScanCompletedListener() { @Override public void onScanCompleted(String path, Uri uri) { - if(uri == null) - uri = FileProvider.getUriForFile(context,context.getPackageName()+".provider",new File(path)); - listener.onScanCompleted(path,uri); + if (uri == null) + uri = FileProvider.getUriForFile(context, context.getPackageName() + ".provider", new File(path)); + listener.onScanCompleted(path, uri); } }); } diff --git a/app/src/main/java/com/mugames/vidsnap/ui/activities/MainActivity.java b/app/src/main/java/com/mugames/vidsnap/ui/activities/MainActivity.java index ff0480d..006c4ea 100644 --- a/app/src/main/java/com/mugames/vidsnap/ui/activities/MainActivity.java +++ b/app/src/main/java/com/mugames/vidsnap/ui/activities/MainActivity.java @@ -19,9 +19,11 @@ import android.Manifest; import android.app.Activity; +import android.app.PendingIntent; import android.content.ActivityNotFoundException; import android.content.DialogInterface; import android.content.Intent; +import android.content.IntentSender; import android.content.pm.PackageManager; import android.media.MediaScannerConnection; import android.net.Uri; @@ -53,6 +55,7 @@ import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; +import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProvider; @@ -71,6 +74,7 @@ import com.mugames.vidsnap.network.Downloader; import com.mugames.vidsnap.network.MiniExecute; import com.mugames.vidsnap.storage.AppPref; +import com.mugames.vidsnap.utility.VideoSharedBroadcast; import com.mugames.vidsnap.utility.bundles.DownloadDetails; import com.mugames.vidsnap.utility.DownloadReceiver; import com.mugames.vidsnap.storage.FileUtil; @@ -145,7 +149,7 @@ protected void onCreate(Bundle savedInstanceState) { activityViewModel = new ViewModelProvider(this).get(MainActivityViewModel.class); - activityViewModel.getActiveDownload().observe(this,this::setupBadge); + activityViewModel.getActiveDownload().observe(this, this::setupBadge); String[] themes = getResources().getStringArray(R.array.theme_values); String theme = getStringValue(R.string.key_Theme, themes[0]); @@ -161,9 +165,8 @@ else if (theme.equals(themes[2])) storageSwitcher = new StorageSwitcher(this); - for (DownloadDetails details : activityViewModel.getDownloadDetailsList()) { - ((DownloadReceiver) details.receiver).setDialogInterface(this); - } + activityViewModel.getDownloadFailedResponseLiveData().observe(this, result -> + error(result.getResponse(), result.getException())); locationResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback() { @@ -171,7 +174,7 @@ else if (theme.equals(themes[2])) public void onActivityResult(ActivityResult result) { if (result.getResultCode() == Activity.RESULT_OK) { setPath(result.getData()); - }else { + } else { activityViewModel.tempDetails.clear(); } } @@ -226,7 +229,7 @@ public void onReceiveData(boolean isUpdateAvailable, String v, boolean isForced, gotIntent(getIntent()); } - if(activityViewModel.isProgressDialogVisible()){ + if (activityViewModel.isProgressDialogVisible()) { dialog.show(activityViewModel.getProgressDialogText()); } @@ -248,8 +251,7 @@ protected void onNewIntent(Intent intent) { void gotIntent(Intent intent) { if (intent == null) return; String content = MainActivityViewModel.intentString(intent); - - +// content = "https://youtu.be/rpOjFiV0X6k"; if (content != null) replaceFragment(VideoFragment.newInstance(content), VideoFragment.class.getName()); @@ -354,7 +356,7 @@ public void replaceFragment(Fragment fragment, String tag) { boolean isRunning = manager.popBackStackImmediate(tag, 0); Fragment runningFragment = manager.findFragmentByTag(tag); - if(isInstanceof(runningFragment,fragment,StatusFragment.class)) + if (isInstanceof(runningFragment, fragment, StatusFragment.class)) manager.popBackStack(); @@ -429,15 +431,16 @@ public void setupBadge(Integer size) { } } - void safeDismissPopUp(){ - if(dialog!=null) + void safeDismissPopUp() { + activityViewModel.setProgressDialogState(false, null); + if (dialog != null) dialog.dismiss(); } @Override protected void onDestroy() { safeDismissPopUp(); - if(circularProgressDialog!=null) circularProgressDialog.dismiss(); + if (circularProgressDialog != null) circularProgressDialog.dismiss(); circularProgressDialog = null; dialog = null; super.onDestroy(); @@ -455,8 +458,8 @@ public boolean onMenuItemClick(MenuItem item) { @Override public void show(String text) { - activityViewModel.setProgressDialogState(true,text); - if(dialog==null) dialog = new PopUpDialog(this); + activityViewModel.setProgressDialogState(true, text); + if (dialog == null) dialog = new PopUpDialog(this); dialog.show(text); } @@ -503,7 +506,7 @@ public void error(String reason, Exception e) { @Override public void dismiss() { - activityViewModel.setProgressDialogState(false,null); + activityViewModel.setProgressDialogState(false, null); safeDismissPopUp(); } @@ -663,7 +666,7 @@ private void fetchSOFiles() { private void downloadAdditionalModule(long size, String url) { safeDismissPopUp(); - if(!activityViewModel.isDownloadingSOFile()){ + if (!activityViewModel.isDownloadingSOFile()) { MaterialAlertDialogBuilder dialogBuilder = new MaterialAlertDialogBuilder(this); dialogBuilder.setTitle("Additional required"); dialogBuilder.setMessage(String.format("Additional file (%s) needed to be download to use %s downloader. Would you like to download it ?", UtilityClass.formatFileSize(size, false), activityViewModel.tempDetails.get(0).src)); @@ -676,7 +679,7 @@ private void downloadAdditionalModule(long size, String url) { activityViewModel.tempDetails.clear(); }); dialogBuilder.create().show(); - }else { + } else { addDownloadListener(); } } @@ -695,10 +698,10 @@ public void onDownloadEnded() { private void addDownloadListener() { circularProgressDialog = new CircularProgressDialog(this); activityViewModel.getDownloadProgressLiveData().observe(this, integer -> { - if(circularProgressDialog!=null) circularProgressDialog.setProgress(integer); + if (circularProgressDialog != null) circularProgressDialog.setProgress(integer); }); - activityViewModel.getDownloadStatusLiveData().observe(this,s -> { - if(circularProgressDialog!=null) circularProgressDialog.setStatusTxt(s); + activityViewModel.getDownloadStatusLiveData().observe(this, s -> { + if (circularProgressDialog != null) circularProgressDialog.setStatusTxt(s); }); } @@ -716,9 +719,9 @@ boolean isValidDownloadPath() { private void realDownload() { safeDismissPopUp(); - for (DownloadDetails details:activityViewModel.tempDetails) { + for (DownloadDetails details : activityViewModel.tempDetails) { Intent download = detailsToIntent(details); - if(details.isShareOnlyDownload){ + if (details.isShareOnlyDownload) { addShareOnlyListener(); } ContextCompat.startForegroundService(this, download); @@ -742,7 +745,7 @@ Intent detailsToIntent(DownloadDetails details) { details.id = id; details.receiver = new DownloadReceiver(new Handler(Looper.getMainLooper()), - this, this, + this, id, activityViewModel); @@ -755,17 +758,17 @@ Intent detailsToIntent(DownloadDetails details) { @Override - public void signInNeeded(String reason, String loginURL, String[] loginDoneUrl, int cookiesKey, UtilityInterface.LoginIdentifier identifier) { + public void signInNeeded(UtilityClass.LoginDetailsProvider loginDetailsProvider) { runOnUiThread(() -> { safeDismissPopUp(); MaterialAlertDialogBuilder dialog = new MaterialAlertDialogBuilder(this) .setTitle("Seems to be Private Video!") .setCancelable(false) - .setMessage(reason) - .setPositiveButton("Sign-in", (dialog1, which) -> openWebPage(loginURL, loginDoneUrl, null, cookies -> { - setStringValue(cookiesKey, cookies); - identifier.loggedIn(cookies); + .setMessage(loginDetailsProvider.getReason()) + .setPositiveButton("Sign-in", (dialog1, which) -> openWebPage(loginDetailsProvider.getLoginURL(), loginDetailsProvider.getLoginDoneUrl(), null, cookies -> { + setStringValue(loginDetailsProvider.getCookiesKey(), cookies); + loginDetailsProvider.getIdentifier().loggedIn(cookies); })) .setNegativeButton("No,Thanks", (dialog12, which) -> error("URL invalid or Login Permission Dined", null)); dialog.show(); @@ -824,7 +827,7 @@ public void onClick(DialogInterface dialog, int which) { public void logOutInsta(ConfigurationCallback configurationCallback) { - openWebPage("https://instagram.com/accounts/logout/", new String[]{"https://www.instagram.com/"}, getStringValue(R.string.key_instagram, null), new UtilityInterface.CookiesInterface() { + openWebPage("https://instagram.com/accounts/logout/", new String[]{"https://www.instagram.com/", "https://www.instagram.com/accounts/login/?next=/accounts/logout/"}, getStringValue(R.string.key_instagram, null), new UtilityInterface.CookiesInterface() { @Override public void onReceivedCookies(String cookies) { Toast.makeText(MainActivity.this, "Logout Successful", Toast.LENGTH_SHORT).show(); @@ -842,38 +845,57 @@ public void logOutFB(ConfigurationCallback configurationCallback) { }); } - public void updateShareDownloadProgress(int progress, String msg){ - if(circularProgressDialog ==null) return; + public void updateShareDownloadProgress(int progress, String msg) { + if (circularProgressDialog == null) return; circularProgressDialog.setProgress(progress); - Log.e(TAG, "updateShareDownloadProgress: "+msg ); + Log.e(TAG, "updateShareDownloadProgress: " + msg); circularProgressDialog.setStatusTxt(msg); } - public void shareDownloadedVideo(Bundle resultData) { - if(circularProgressDialog!=null)circularProgressDialog.dismiss(); + private void shareDownloadedVideo(Bundle resultData) { + if (circularProgressDialog != null) circularProgressDialog.dismiss(); circularProgressDialog = null; activityViewModel.setTempResultBundle(resultData); Intent intent = new Intent(Intent.ACTION_SEND); intent.setType(resultData.getString(FILE_MIME)); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.putExtra(Intent.EXTRA_STREAM, Uri.parse(resultData.getString(OUTFILE_URI))); - shareResultLauncher.launch(Intent.createChooser(intent, "Select Social Media")); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { + Intent receiver = new Intent(this, VideoSharedBroadcast.class); + receiver.putExtra(VideoSharedBroadcast.RESULT_BUNDLE, resultData); + PendingIntent pendingIntent; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) + pendingIntent = PendingIntent.getBroadcast(this, 0, receiver, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); + else + pendingIntent = PendingIntent.getBroadcast(this, 0, receiver, PendingIntent.FLAG_UPDATE_CURRENT); + startActivity(Intent.createChooser(intent, "Choose application to share", pendingIntent.getIntentSender())); + } else { + shareResultLauncher.launch(Intent.createChooser(intent, "Choose application to share")); + } } private void addShareOnlyListener() { int id = activityViewModel.shareOnlyDownloadStatus(); - if(id == MainActivityViewModel.NO_SHARE_ONLY_FILES_DOWNLOADING){ + if (id == MainActivityViewModel.NO_SHARE_ONLY_FILES_DOWNLOADING) { return; } + Observer observer = new Observer() { + @Override + public void onChanged(Bundle bundle) { + shareDownloadedVideo(bundle); + activityViewModel.getShareOnlyDownloadLiveData().removeObserver(this); + } + }; + activityViewModel.getShareOnlyDownloadLiveData().observe(this, observer); circularProgressDialog = new CircularProgressDialog(this); - ((DownloadReceiver)DownloadDetails.findDetails(id).receiver).getResultBundle().observe(this,bundle -> { + ((DownloadReceiver) DownloadDetails.findDetails(id).receiver).getResultBundle().observe(this, bundle -> { String msg; int resultCode = bundle.getInt(RESULT_CODE); - if (resultCode == PROGRESS_UPDATE_AUDIO) msg="Downloading Audio"; - else if (resultCode == PROGRESS_UPDATE_VIDEO) msg="Downloading Video"; - else if (resultCode == PROGRESS_UPDATE_MERGING) msg="Merging"; + if (resultCode == PROGRESS_UPDATE_AUDIO) msg = "Downloading Audio"; + else if (resultCode == PROGRESS_UPDATE_VIDEO) msg = "Downloading Video"; + else if (resultCode == PROGRESS_UPDATE_MERGING) msg = "Merging"; else msg = bundle.getString(FETCH_MESSAGE); - updateShareDownloadProgress(bundle.getInt(PROGRESS),msg); + updateShareDownloadProgress(bundle.getInt(PROGRESS), msg); }); } } \ No newline at end of file diff --git a/app/src/main/java/com/mugames/vidsnap/ui/fragments/DownloadFragment.java b/app/src/main/java/com/mugames/vidsnap/ui/fragments/DownloadFragment.java index d3e7c7e..6b33493 100644 --- a/app/src/main/java/com/mugames/vidsnap/ui/fragments/DownloadFragment.java +++ b/app/src/main/java/com/mugames/vidsnap/ui/fragments/DownloadFragment.java @@ -76,7 +76,10 @@ public View onCreateView(@NotNull LayoutInflater inflater, ViewGroup container, View view; - activityViewModel = new ViewModelProvider(getActivity(),ViewModelProvider.AndroidViewModelFactory.getInstance(getActivity().getApplication())).get(MainActivityViewModel.class); + activityViewModel = new ViewModelProvider( + requireActivity(), + (ViewModelProvider.Factory) ViewModelProvider.AndroidViewModelFactory.getInstance(requireActivity().getApplication()) + ).get(MainActivityViewModel.class); view = inflater.inflate(R.layout.fragment_downloading, container, false); @@ -95,7 +98,7 @@ public void onChanged(ArrayList downloadDetails) { } }); if (!activityViewModel.getDownloadDetailsList().isEmpty()) { - recyclerView.setBackgroundColor(ContextCompat.getColor(getActivity(), R.color.list_background)); + recyclerView.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.list_background)); } adapter.submitList(activityViewModel.getDownloadDetailsList()); recyclerView.setAdapter(adapter); diff --git a/app/src/main/java/com/mugames/vidsnap/ui/fragments/HistoryFragment.java b/app/src/main/java/com/mugames/vidsnap/ui/fragments/HistoryFragment.java index 64636ec..dcafb10 100644 --- a/app/src/main/java/com/mugames/vidsnap/ui/fragments/HistoryFragment.java +++ b/app/src/main/java/com/mugames/vidsnap/ui/fragments/HistoryFragment.java @@ -60,7 +60,11 @@ public static HistoryFragment newInstance() { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - historyViewModel = new ViewModelProvider(this,ViewModelProvider.AndroidViewModelFactory.getInstance(getActivity().getApplication())).get(HistoryViewModel.class); + historyViewModel = new ViewModelProvider(this, + (ViewModelProvider.Factory) ViewModelProvider + .AndroidViewModelFactory + .getInstance(requireActivity().getApplication()) + ).get(HistoryViewModel.class); } @Override @@ -72,7 +76,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Context context = view.getContext(); RecyclerView recyclerView = (RecyclerView) view; recyclerView.setLayoutManager(new LinearLayoutManager(context)); - recyclerView.setBackgroundColor(ContextCompat.getColor(getActivity(), R.color.list_background)); + recyclerView.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.list_background)); historyViewModel.getAllValues().observe(getViewLifecycleOwner(), new Observer>() { @Override public void onChanged(List histories) { diff --git a/app/src/main/java/com/mugames/vidsnap/ui/fragments/QualityFragment.java b/app/src/main/java/com/mugames/vidsnap/ui/fragments/QualityFragment.java index addb0b4..2e5a0a8 100644 --- a/app/src/main/java/com/mugames/vidsnap/ui/fragments/QualityFragment.java +++ b/app/src/main/java/com/mugames/vidsnap/ui/fragments/QualityFragment.java @@ -17,23 +17,40 @@ package com.mugames.vidsnap.ui.fragments; +import static com.mugames.vidsnap.storage.FileUtil.removeStuffFromName; + +import android.content.DialogInterface; import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.net.Uri; import android.os.Bundle; import androidx.annotation.Nullable; import androidx.annotation.NonNull; +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.target.CustomTarget; +import com.bumptech.glide.request.transition.Transition; import com.google.android.material.bottomsheet.BottomSheetDialogFragment; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.mugames.vidsnap.R; +import com.mugames.vidsnap.storage.AppPref; +import com.mugames.vidsnap.ui.activities.MainActivity; +import com.mugames.vidsnap.ui.viewmodels.VideoFragmentViewModel; +import com.mugames.vidsnap.utility.MIMEType; import com.mugames.vidsnap.utility.Statics; import com.mugames.vidsnap.utility.UtilityInterface; +import com.mugames.vidsnap.utility.bundles.DownloadDetails; +import com.mugames.vidsnap.utility.bundles.Formats; +import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.webkit.MimeTypeMap; import android.widget.Button; import android.widget.EditText; import android.widget.ImageView; @@ -50,26 +67,18 @@ public class QualityFragment extends BottomSheetDialogFragment { String TAG = Statics.TAG + ":QualityFragment"; - ArrayList qualityList; - ArrayList sizesList; - int selectedItem = -1; Bitmap thumbNail; EditText editText; - private UtilityInterface.DownloadUICallBack callback; + Formats formats; + VideoFragmentViewModel viewModel; + UtilityInterface.DownloadClickedCallback downloadClickedCallback; - public static QualityFragment newInstance(ArrayList qualitiesList, ArrayList sizes) { - final QualityFragment fragment = new QualityFragment(); - fragment.qualityList = qualitiesList; - fragment.sizesList = sizes; - return fragment; + public QualityFragment() { } - public void setRequired(UtilityInterface.DownloadUICallBack callBack) { - this.callback = callBack; - } public void setThumbNail(Bitmap thumbNail) { this.thumbNail = thumbNail; @@ -84,14 +93,21 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + viewModel = new ViewModelProvider(requireActivity()).get(VideoFragmentViewModel.class); + + formats = viewModel.getFormats(); final RecyclerView recyclerView = view.findViewById(R.id.formats); recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); - qualityAdapter adapter = new qualityAdapter(qualityList.size(), qualityList, sizesList); - + QualityAdapter adapter = new QualityAdapter(formats.qualities, formats.videoSizeInString); recyclerView.setAdapter(adapter); + + if (savedInstanceState != null) { + setThumbNail(viewModel.getDownloadDetails().getThumbNail()); + } + Button download_mp_4 = view.findViewById(R.id.download_mp4); Button download_mp_3 = view.findViewById(R.id.download_mp3); Button share = view.findViewById(R.id.share); @@ -101,30 +117,107 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat editText = view.findViewById(R.id.edit_name); download_mp_4.setOnClickListener(v -> { - callback.onDownloadMP4ButtonPressed(editText.getText().toString()); - QualityFragment.this.dismiss(); + downloadVideo(editText.getText().toString(), false); }); - download_mp_3.setOnClickListener(v->{ - callback.onDownloadMP3ButtonPressed(editText.getText().toString()); + download_mp_3.setOnClickListener(v -> { + downloadMp3(editText.getText().toString()); }); share.setOnClickListener(v -> { - callback.onShareButtonPressed(editText.getText().toString()); + downloadVideo(editText.getText().toString(), true); }); } + void downloadVideo(String fileName, boolean isOnlyShare) { + if (downloadClickedCallback != null) + downloadClickedCallback.onDownloadButtonPressed(); + dismiss(); + ArrayList downloadDetails = new ArrayList<>(); + viewModel.getDownloadDetails().isShareOnlyDownload = isOnlyShare; + viewModel.getDownloadDetails().fileName = removeStuffFromName(fileName); + downloadDetails.add(viewModel.getDownloadDetails()); + ((MainActivity) requireActivity()).download(downloadDetails); + } - public void setName(String name) { + private void setName(String name) { editText.setText(name); } -// @Override -// public int getTheme() { -// return R.style.CustomBottomSheetDialog; -// } + public void downloadMp3(String fileName) { + if (downloadClickedCallback != null) + downloadClickedCallback.onDownloadButtonPressed(); + dismiss(); + ArrayList downloadDetails = new ArrayList<>(); + if (!viewModel.getFormats().audioURLs.isEmpty()) { + fileName = removeStuffFromName(fileName + "_mp3 version"); + viewModel.getDownloadDetails().fileName = fileName.split("\\.")[0]; + viewModel.getDownloadDetails().fileType = "mp3"; + viewModel.getDownloadDetails().fileMime = viewModel.getDownloadDetails().mimeAudio; + viewModel.getDownloadDetails().audioURL = null; + viewModel.getDownloadDetails().videoSize = viewModel.getDownloadDetails().audioSize; + viewModel.getDownloadDetails().fileMime = MIMEType.AUDIO_MP4; + viewModel.getDownloadDetails().videoURL = viewModel.getFormats().audioURLs.get(0); + downloadDetails.add(viewModel.getDownloadDetails()); + ((MainActivity) requireActivity()).download(downloadDetails); + } else { + new MaterialAlertDialogBuilder(requireContext()) + .setTitle("Under Construction") + .setMessage("Mp3 feature other than YouTube is currently under development.\nStay tuned, cheers") + .setPositiveButton("Good Job!", null) + .setCancelable(true) + .show(); + } + + } + + + public void onSelectedItem(int position) { + DownloadDetails details = viewModel.getDownloadDetails(); + Formats formats = viewModel.getFormats(); + try { + details.chunkUrl = formats.chunkUrlList.get(position); + details.chunkCount = formats.manifest.get(position).size(); + } catch (IndexOutOfBoundsException e) { + e.printStackTrace(); + } + + try { + details.mimeAudio = formats.audioMime.get(position); + } catch (IndexOutOfBoundsException e) { + e.printStackTrace(); + } + + details.fileMime = formats.fileMime.get(position); + details.fileType = MimeTypeMap.getSingleton().getExtensionFromMimeType(details.fileMime); + + details.videoSize = formats.videoSizes.get(position); + try { + details.audioURL = formats.audioURLs.get(0); + details.audioSize = formats.audioSizes.get(0); + } catch (IndexOutOfBoundsException e) { + details.audioURL = null; + } + try { + details.videoURL = formats.mainFileURLs.get(position); + } catch (IndexOutOfBoundsException e) { + e.printStackTrace(); + } + String quality = formats.qualities.get(position); + formats.title = removeStuffFromName(formats.title); + if (quality.equals("--")) quality = ""; + details.pathUri = AppPref.getInstance(getContext()).getSavePath(); + details.fileName = formats.title + "_" + quality + "_"; + details.src = formats.src; + setName(details.fileName + "." + details.fileType); + } + + public void setOnDownloadButtonClicked(UtilityInterface.DownloadClickedCallback downloadClickedCallback) { + this.downloadClickedCallback = downloadClickedCallback; + } + - private class ViewHolder extends RecyclerView.ViewHolder { + private static class ViewHolder extends RecyclerView.ViewHolder { final TextView qualityLabel; final TextView sizeText; @@ -138,15 +231,15 @@ private class ViewHolder extends RecyclerView.ViewHolder { } } - private class qualityAdapter extends RecyclerView.Adapter { + private class QualityAdapter extends RecyclerView.Adapter { private final int mItemCount; final ArrayList mQualities; final ArrayList mSizes; ArrayList radioButtons = new ArrayList<>(); - qualityAdapter(int itemCount, ArrayList qualities, ArrayList sizes) { - mItemCount = itemCount; + QualityAdapter(ArrayList qualities, ArrayList sizes) { + mItemCount = qualities.size(); mQualities = qualities; mSizes = sizes; } @@ -169,7 +262,7 @@ public void onBindViewHolder(ViewHolder holder, int position) { if (radioButtons.size() == 0) { holder.radioButton.setChecked(true); - callback.onSelectedItem(0, QualityFragment.this); + onSelectedItem(0); selectedItem = 0; } else holder.radioButton.setChecked(false); @@ -202,7 +295,7 @@ void itemCheckChanged(View v) { radioButton.setChecked(false); } radioButtons.get(selectedItem).setChecked(true); - callback.onSelectedItem(selectedItem, QualityFragment.this); + onSelectedItem(selectedItem); } } } \ No newline at end of file diff --git a/app/src/main/java/com/mugames/vidsnap/ui/fragments/StatusFragment.java b/app/src/main/java/com/mugames/vidsnap/ui/fragments/StatusFragment.java index 6bb344a..5512744 100644 --- a/app/src/main/java/com/mugames/vidsnap/ui/fragments/StatusFragment.java +++ b/app/src/main/java/com/mugames/vidsnap/ui/fragments/StatusFragment.java @@ -78,7 +78,7 @@ /** * A fragment that is opened when user selected status from {@link HomeFragment} */ -public class StatusFragment extends Fragment implements AdapterView.OnItemSelectedListener, UtilityInterface.AnalyzeUICallback { +public class StatusFragment extends Fragment implements AdapterView.OnItemSelectedListener { LinearLayout urlLayout; Button saveButton; @@ -141,7 +141,9 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, View view = inflater.inflate(R.layout.fragment_status_list, container, false); viewModel = new ViewModelProvider(this).get(StatusFragmentViewModel.class); - viewModel.setAnalyzeUICallback(this); + viewModel.getFormatsLiveData().observe(getViewLifecycleOwner(),formats -> { + onAnalyzeCompleted(); + }); Context context = view.getContext(); recyclerView = view.findViewById(R.id.status_recycler); @@ -212,10 +214,9 @@ public void onNothingSelected(AdapterView adapterView) { } - @Override - public void onAnalyzeCompleted(boolean isMultipleFile) { + public void onAnalyzeCompleted() { adapter = new DownloadableAdapter(this, viewModel.getFormats()); - adapter.getSelectedList().observe(this, this::selectedListChanged); + adapter.getSelectedList().observe(getViewLifecycleOwner(), this::selectedListChanged); recyclerView.setAdapter(adapter); } diff --git a/app/src/main/java/com/mugames/vidsnap/ui/fragments/VideoFragment.java b/app/src/main/java/com/mugames/vidsnap/ui/fragments/VideoFragment.java index 17c327a..5915634 100644 --- a/app/src/main/java/com/mugames/vidsnap/ui/fragments/VideoFragment.java +++ b/app/src/main/java/com/mugames/vidsnap/ui/fragments/VideoFragment.java @@ -17,6 +17,11 @@ package com.mugames.vidsnap.ui.fragments; +import static android.content.Context.INPUT_METHOD_SERVICE; +import static com.mugames.vidsnap.storage.FileUtil.removeStuffFromName; +import static com.mugames.vidsnap.ui.viewmodels.VideoFragmentViewModel.URL_KEY; +import static com.mugames.vidsnap.utility.UtilityInterface.TouchCallback; + import android.graphics.Bitmap; import android.graphics.Rect; import android.graphics.drawable.Drawable; @@ -35,6 +40,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; +import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -43,32 +49,25 @@ import com.bumptech.glide.request.FutureTarget; import com.bumptech.glide.request.target.CustomTarget; import com.bumptech.glide.request.transition.Transition; -import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.firebase.crashlytics.FirebaseCrashlytics; +import com.mugames.vidsnap.R; import com.mugames.vidsnap.storage.AppPref; -import com.mugames.vidsnap.utility.MIMEType; -import com.mugames.vidsnap.utility.UtilityClass; -import com.mugames.vidsnap.ui.viewmodels.VideoFragmentViewModel; import com.mugames.vidsnap.ui.activities.MainActivity; -import com.mugames.vidsnap.R; +import com.mugames.vidsnap.ui.adapters.DownloadableAdapter; +import com.mugames.vidsnap.ui.viewmodels.VideoFragmentViewModel; +import com.mugames.vidsnap.utility.Statics; +import com.mugames.vidsnap.utility.UtilityClass; import com.mugames.vidsnap.utility.bundles.DownloadDetails; import com.mugames.vidsnap.utility.bundles.Formats; -import com.mugames.vidsnap.utility.Statics; -import com.mugames.vidsnap.ui.adapters.DownloadableAdapter; import java.util.ArrayList; import java.util.concurrent.ExecutionException; -import static android.content.Context.INPUT_METHOD_SERVICE; -import static com.mugames.vidsnap.storage.FileUtil.removeStuffFromName; -import static com.mugames.vidsnap.utility.UtilityInterface.*; -import static com.mugames.vidsnap.ui.viewmodels.VideoFragmentViewModel.URL_KEY; - /** * A fragment that is opened when user selected video from {@link HomeFragment} */ public class VideoFragment extends Fragment implements - AnalyzeUICallback, TouchCallback, DownloadUICallBack { + TouchCallback { String TAG = Statics.TAG + ":VideoFragment"; @@ -87,10 +86,10 @@ public class VideoFragment extends Fragment implements long size; - - private boolean isPaused; + boolean isRecreated; VideoFragmentViewModel viewModel; + String link; private QualityFragment dialogFragment = null; @@ -111,11 +110,18 @@ public View onCreateView( View view = inflater.inflate(R.layout.fragment_video, container, false); - activity = (MainActivity) getActivity(); + activity = (MainActivity) requireActivity(); activity.setTouchCallback(this); - viewModel = new ViewModelProvider(this).get(VideoFragmentViewModel.class); - viewModel.setAnalyzeUICallback(this); + isRecreated = savedInstanceState != null; + + viewModel = new ViewModelProvider(requireActivity()).get(VideoFragmentViewModel.class); + viewModel.getFormatsLiveData().observe(getViewLifecycleOwner(), formats -> { + if (!isRecreated) + onAnalyzeCompleted(formats); + isRecreated = false; + }); + analysis = view.findViewById(R.id.analysis); urlBox = view.findViewById(R.id.url); @@ -123,18 +129,18 @@ public View onCreateView( button = view.findViewById(R.id.card_selected); button.setVisibility(View.GONE); - button.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - dialogFragment = null; - urlBox.setText(""); - new Thread(() -> actionForMOREFile()).start();// Running is separate thread to load image sync.. with Glide - resetMultiVideoList(); - } + button.setOnClickListener(v -> { + // Running is separate thread to load image sync.. with Glide + urlBox.setText(""); + new Thread(this::actionForMOREFile).start(); + resetMultiVideoList(); }); - String link = getArguments() != null ? getArguments().getString(URL_KEY) : null; + link = getArguments() != null ? getArguments().getString(URL_KEY) : null; + if (link == null) + link = viewModel.getUrlLink(); + urlBox.setText(link); analysis.setOnClickListener(v -> { if (adapter != null) resetMultiVideoList(); @@ -150,17 +156,49 @@ public void onClick(View v) { viewModel.updateActivityReference(activity); - if (link != null) startProcess(link); + if (link != null && savedInstanceState == null && !link.equals(viewModel.getUrlLink())) { + urlBox.setText(link); + startProcess(link); + } return view; } + private boolean isNewLink() { + return link.equals(viewModel.getUrlLink()); + } + public void startProcess(String link) { - urlBox.setText(link); + if (link == null) return; + if (urlBox != null) urlBox.setText(link); + if (getArguments() != null) { + getArguments().putString(URL_KEY, link); + } + this.link = link; safeDismissBottomSheet(); - if (viewModel.onClickAnalysis(urlBox.getText().toString(), (MainActivity) getActivity()) == null) - unLockAnalysis(); - else setCrashCauseLink(link); // Only if it is valid URL it reach here + if (viewModel != null) { + if (viewModel.onClickAnalysis(link, (MainActivity) getActivity()) == null) + unLockAnalysis(); + else { + // Only if it is valid URL it reach here + viewModel.getLoginDetailsProviderLiveData().observe(getViewLifecycleOwner(), new Observer() { + @Override + public void onChanged(UtilityClass.LoginDetailsProvider loginDetailsProvider) { + if (loginDetailsProvider == null) return; + + viewModel.getDownloadDetails().srcUrl = link; + safeDismissBottomSheet(); + urlBox.setText(""); + + viewModel.clearLoginAlert(); + viewModel.getLoginDetailsProviderLiveData().removeObserver(this); + ((MainActivity) VideoFragment.this.requireActivity()).signInNeeded(loginDetailsProvider); + } + } + ); + setCrashCauseLink(link); + } + } } private void hideKeyboard(View v) { @@ -177,17 +215,16 @@ private void hideKeyboard(View v) { void safeDismissBottomSheet() { try { dialogFragment.dismiss(); - } catch (IllegalArgumentException | NullPointerException e) { - Log.e(TAG, "safeDismissBottomSheet: ",e ); + } catch (IllegalStateException | NullPointerException e) { + e.printStackTrace(); } dialogFragment = null; } void setCrashCauseLink(String link) { - Fragment fragment = activity.getSupportFragmentManager().findFragmentByTag("TAG"); - if (fragment != null) - ((QualityFragment) fragment).dismiss(); + if (dialogFragment != null) + dialogFragment.dismiss(); FirebaseCrashlytics.getInstance().setCustomKey("URL", link); } @@ -201,16 +238,17 @@ public void unLockAnalysis() { } - @Override - public void onAnalyzeCompleted(boolean isMultipleFile) { + public void onAnalyzeCompleted(Formats formats) { unLockAnalysis(); - if (isMultipleFile) { + if (viewModel.isRecreated() && !isNewLink()) return; + viewModel.nullifyExtractor(); + if (formats.isMultipleFile()) { adapter = new DownloadableAdapter(this, viewModel.getFormats()); - adapter.getSelectedList().observe(this, this::selectedItemChanged); + adapter.getSelectedList().observe(getViewLifecycleOwner(), this::selectedItemChanged); list.setLayoutManager(new GridLayoutManager(activity, 2)); list.setAdapter(adapter); } else { - actionForSOLOFile(); + actionForSOLOFile(formats); } } @@ -233,11 +271,11 @@ void resetMultiVideoList() { } - void actionForSOLOFile() { - Formats formats = viewModel.getFormats(); - - dialogFragment = QualityFragment.newInstance(formats.qualities, formats.videoSizeInString); - dialogFragment.setRequired(this); + void actionForSOLOFile(Formats formats) { + safeDismissBottomSheet(); + if (viewModel.getFormats() == null) return; + QualityFragment dialogFragment = new QualityFragment(); + dialogFragment.setOnDownloadButtonClicked(this::safeDismissBottomSheet); Glide.with(requireContext()) .asBitmap() @@ -247,9 +285,8 @@ void actionForSOLOFile() { public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition transition) { viewModel.getDownloadDetails().setThumbNail(getContext(), resource); dialogFragment.setThumbNail(resource); - if (!isPaused) - dialogFragment.show(activity.getSupportFragmentManager(), "TAG"); - + VideoFragment.this.dialogFragment = dialogFragment; + dialogFragment.show(requireActivity().getSupportFragmentManager(), "TAG"); } @Override @@ -259,31 +296,14 @@ public void onLoadCleared(@Nullable Drawable placeholder) { }); } - - @Override - public void onPause() { - super.onPause(); - isPaused = true; - safeDismissBottomSheet(); - } - - @Override - public void onResume() { - super.onResume(); - isPaused = false; - try { - if (viewModel.getFormats() != null && !viewModel.getFormats().isMultipleFile()) { - if (dialogFragment == null) actionForSOLOFile(); - else dialogFragment.show(activity.getSupportFragmentManager(), "TAG"); - } - } catch (IllegalStateException e) {} - - } - @Override public void onDestroy() { - safeDismissBottomSheet(); super.onDestroy(); + if (viewModel != null) + viewModel.removeActivityReference(); + activity.setTouchCallback(null); + Log.e(TAG, "onDestroy: called"); + dialogFragment = null; } @Override @@ -309,7 +329,7 @@ public void actionForMOREFile() { details.videoURL = viewModel.getFormats().mainFileURLs.get(index); viewModel.getFormats().title = removeStuffFromName(viewModel.getFormats().title); - FutureTarget target = Glide.with(getContext()) + FutureTarget target = Glide.with(requireContext()) .asBitmap() .load(viewModel.getFormats().thumbNailsURL.get(index)) .submit(); @@ -327,106 +347,12 @@ public void actionForMOREFile() { downloadDetails.add(details); } - activity.runOnUiThread(() -> activity.download(downloadDetails)); - - } - - void updateUiOnDownloadPressed(){ - viewModel.getDownloadDetails().srcUrl = urlBox.getText().toString(); - safeDismissBottomSheet(); - dialogFragment = null; - urlBox.setText(""); } @Override - public void onDownloadMP4ButtonPressed(String fileName) { - updateUiOnDownloadPressed(); - downloadVideo(fileName,false); - - } - - void downloadVideo(String fileName, boolean isOnlyShare){ - viewModel.nullifyFormats(); - ArrayList downloadDetails = new ArrayList<>(); - fileName = removeStuffFromName(fileName); - viewModel.getDownloadDetails().isShareOnlyDownload = isOnlyShare; - viewModel.getDownloadDetails().fileName = fileName.split("\\.")[0]; - downloadDetails.add(viewModel.getDownloadDetails()); - activity.download(downloadDetails); - } - - @Override - public void onDownloadMP3ButtonPressed(String fileName) { - updateUiOnDownloadPressed(); - ArrayList downloadDetails = new ArrayList<>(); - if(!viewModel.getFormats().audioURLs.isEmpty()){ - fileName = removeStuffFromName(fileName+"_mp3 version"); - viewModel.getDownloadDetails().fileName = fileName.split("\\.")[0]; - viewModel.getDownloadDetails().fileType = "mp3"; - viewModel.getDownloadDetails().fileMime = viewModel.getDownloadDetails().mimeAudio; - viewModel.getDownloadDetails().audioURL = null; - viewModel.getDownloadDetails().videoSize = viewModel.getDownloadDetails().audioSize; - viewModel.getDownloadDetails().fileMime = MIMEType.AUDIO_MP4; - viewModel.getDownloadDetails().videoURL = viewModel.getFormats().audioURLs.get(0); - downloadDetails.add(viewModel.getDownloadDetails()); - activity.download(downloadDetails); - }else { - new MaterialAlertDialogBuilder(requireContext()) - .setTitle("Under Construction") - .setMessage("Mp3 feature other than YouTube is currently under development.\nStay tuned, cheers") - .setPositiveButton("Good Job!",null) - .setCancelable(true) - .show(); - } - viewModel.nullifyFormats(); - - } - - @Override - public void onShareButtonPressed(String fileName) { - updateUiOnDownloadPressed(); - downloadVideo(fileName,true); - } - - @Override - public void onSelectedItem(int position, QualityFragment qualityFragment) { - DownloadDetails details = viewModel.getDownloadDetails(); - Formats formats = viewModel.getFormats(); - try { - details.chunkUrl = formats.chunkUrlList.get(position); - details.chunkCount = formats.manifest.get(position).size(); - } catch (IndexOutOfBoundsException e) { - e.printStackTrace(); - } - - try { - details.mimeAudio = formats.audioMime.get(position); - } catch (IndexOutOfBoundsException e) { - e.printStackTrace(); - } - - details.fileMime = formats.fileMime.get(position); - details.fileType = MimeTypeMap.getSingleton().getExtensionFromMimeType(details.fileMime); - - details.videoSize = formats.videoSizes.get(position); - try { - details.audioURL = formats.audioURLs.get(0); - details.audioSize = formats.audioSizes.get(0); - } catch (IndexOutOfBoundsException e) { - details.audioURL = null; - } - try { - details.videoURL = formats.mainFileURLs.get(position); - } catch (IndexOutOfBoundsException e) { - e.printStackTrace(); - } - String quality = formats.qualities.get(position); - formats.title = removeStuffFromName(formats.title); - if (quality.equals("--")) quality = ""; - details.pathUri = AppPref.getInstance(getContext()).getSavePath(); - details.fileName = formats.title + "_" + quality + "_"; - details.src = formats.src; - qualityFragment.setName(details.fileName + "." + details.fileType); + public void onPause() { + super.onPause(); + safeDismissBottomSheet(); } } \ No newline at end of file diff --git a/app/src/main/java/com/mugames/vidsnap/ui/viewmodels/MainActivityViewModel.java b/app/src/main/java/com/mugames/vidsnap/ui/viewmodels/MainActivityViewModel.java index be64a67..9766d53 100644 --- a/app/src/main/java/com/mugames/vidsnap/ui/viewmodels/MainActivityViewModel.java +++ b/app/src/main/java/com/mugames/vidsnap/ui/viewmodels/MainActivityViewModel.java @@ -38,6 +38,7 @@ import com.mugames.vidsnap.firebase.FirebaseManager; import com.mugames.vidsnap.R; +import com.mugames.vidsnap.network.Response; import com.mugames.vidsnap.storage.AppPref; import com.mugames.vidsnap.storage.FileUtil; import com.mugames.vidsnap.utility.bundles.DownloadDetails; @@ -66,6 +67,8 @@ public class MainActivityViewModel extends AndroidViewModel implements UtilityIn MutableLiveData downloadProgressLiveData = new MutableLiveData<>(); MutableLiveData downloadStatusLiveData = new MutableLiveData<>(); + MutableLiveData downloadResponse = new MutableLiveData<>(); + public static final ArrayList downloadDetailsList = new ArrayList<>(); MutableLiveData> downloadDetailsMutableLiveData = new MutableLiveData<>(); @@ -116,8 +119,7 @@ public void addDownloadDetails(DownloadDetails details) { } - synchronized void removeDownloadDetails(int id) { - downloadDetailsList.remove(DownloadDetails.findDetails(id)); + synchronized void removeDownloadDetails() { downloadDetailsMutableLiveData.postValue(downloadDetailsList); activeDownload.postValue(downloadDetailsList.size()); @@ -144,9 +146,31 @@ public int getUniqueDownloadId() { return ran; } + MutableLiveData shareOnlyDownloadLiveData = new MutableLiveData<>(); + @Override - public void onDownloadCompleted(int id) { - removeDownloadDetails(id); + public void onDownloadCompleted(DownloadDetails downloadDetails) { + if (downloadDetails.isShareOnlyDownload) { + shareOnlyDownloadLiveData.setValue(((DownloadReceiver) downloadDetails.receiver).getResultBundle().getValue()); + } + removeDownloadDetails(); + } + + public LiveData getShareOnlyDownloadLiveData() { + return shareOnlyDownloadLiveData; + } + + @Override + public void onDownloadFailed(String reason, Exception e) { + removeDownloadDetails(); + if (e == null) return; + Response response = new Response(e); + response.setResponse(reason); + downloadResponse.setValue(response); + } + + public LiveData getDownloadFailedResponseLiveData() { + return downloadResponse; } @@ -284,4 +308,12 @@ public int shareOnlyDownloadStatus() { } return NO_SHARE_ONLY_FILES_DOWNLOADING; } + + @Override + protected void onCleared() { + super.onCleared(); + for (DownloadDetails details : downloadDetailsList) { + ((DownloadReceiver) details.receiver).setCallback(null); + } + } } diff --git a/app/src/main/java/com/mugames/vidsnap/ui/viewmodels/StatusFragmentViewModel.java b/app/src/main/java/com/mugames/vidsnap/ui/viewmodels/StatusFragmentViewModel.java index 50c0b69..8b471f5 100644 --- a/app/src/main/java/com/mugames/vidsnap/ui/viewmodels/StatusFragmentViewModel.java +++ b/app/src/main/java/com/mugames/vidsnap/ui/viewmodels/StatusFragmentViewModel.java @@ -20,6 +20,8 @@ import androidx.annotation.NonNull; import androidx.lifecycle.AndroidViewModel; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; import com.mugames.vidsnap.extractor.Extractor; import com.mugames.vidsnap.extractor.status.WhatsApp; @@ -34,14 +36,14 @@ public class StatusFragmentViewModel extends AndroidViewModel implements Utility Formats formats; ArrayList selectedList; - UtilityInterface.AnalyzeUICallback analyzeUICallback; + MutableLiveData formatsLiveData = new MutableLiveData<>(); public StatusFragmentViewModel(@NonNull Application application) { super(application); } - public void setAnalyzeUICallback(UtilityInterface.AnalyzeUICallback analyzeUICallback) { - this.analyzeUICallback = analyzeUICallback; + public LiveData getFormatsLiveData() { + return formatsLiveData; } public void searchForStatus(String url, MainActivity activity){ @@ -60,7 +62,7 @@ public void searchForStatus(String url, MainActivity activity){ @Override public void onAnalyzeCompleted(Formats formats) { this.formats =formats; - analyzeUICallback.onAnalyzeCompleted(formats.isMultipleFile()); + formatsLiveData.setValue(formats); } public Formats getFormats() { diff --git a/app/src/main/java/com/mugames/vidsnap/ui/viewmodels/VideoFragmentViewModel.java b/app/src/main/java/com/mugames/vidsnap/ui/viewmodels/VideoFragmentViewModel.java index 817915a..2779518 100644 --- a/app/src/main/java/com/mugames/vidsnap/ui/viewmodels/VideoFragmentViewModel.java +++ b/app/src/main/java/com/mugames/vidsnap/ui/viewmodels/VideoFragmentViewModel.java @@ -4,12 +4,16 @@ import androidx.annotation.NonNull; import androidx.lifecycle.AndroidViewModel; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; import com.mugames.vidsnap.extractor.Facebook; import com.mugames.vidsnap.extractor.Instagram; import com.mugames.vidsnap.extractor.Twitter; import com.mugames.vidsnap.extractor.YouTube; import com.mugames.vidsnap.extractor.Extractor; +import com.mugames.vidsnap.storage.AppPref; +import com.mugames.vidsnap.utility.UtilityClass; import com.mugames.vidsnap.utility.bundles.DownloadDetails; import com.mugames.vidsnap.utility.bundles.Formats; import com.mugames.vidsnap.utility.UtilityInterface; @@ -20,25 +24,27 @@ import java.util.ArrayList; -public class VideoFragmentViewModel extends AndroidViewModel implements UtilityInterface.AnalyzeCallback { +public class VideoFragmentViewModel extends AndroidViewModel implements UtilityInterface.AnalyzeCallback, UtilityInterface.LoginHelper { public static final String URL_KEY = "com.mugames.vidsnap.ui.ViewModels.VideoFragmentViewModel.URL_KEY"; ArrayList selected = new ArrayList<>(); - UtilityInterface.AnalyzeUICallback analyzeUICallback; + + MutableLiveData formatsLiveData = new MutableLiveData<>(); Formats formats; DownloadDetails details; Extractor extractor; - public VideoFragmentViewModel(@NonNull @NotNull Application application) { + String urlLink; + + public VideoFragmentViewModel(@NonNull Application application) { super(application); } - - - public Extractor onClickAnalysis(String url,MainActivity activity){ - formats = new Formats(); + + + public Extractor onClickAnalysis(String url, MainActivity activity) { if (url.contains("youtu")) { extractor = new YouTube(); } else if (url.contains("instagram")) { @@ -49,12 +55,15 @@ public Extractor onClickAnalysis(String url,MainActivity activity){ extractor = new Facebook(); } else { extractor = null; - activity.error("URL Seems to be wrong",null); + activity.error("URL Seems to be wrong", null); } - if(extractor!=null) { + if (extractor != null) { extractor.setContext(getApplication()); extractor.setAnalyzeCallback(this); extractor.setLink(url); + urlLink = url; + formats = null; + extractor.setLoginHelper(this); updateActivityReference(activity); extractor.start(); } @@ -62,6 +71,10 @@ public Extractor onClickAnalysis(String url,MainActivity activity){ } + public String getUrlLink(){ + return urlLink; + } + public ArrayList getSelected() { return selected; } @@ -75,31 +88,56 @@ public Formats getFormats() { return formats; } - public void nullifyFormats(){ - formats = null; + public void nullifyExtractor() { + extractor = null; } - public DownloadDetails getDownloadDetails(){ - if (details==null) details = new DownloadDetails(); + public DownloadDetails getDownloadDetails() { + if (details == null) details = new DownloadDetails(); return details; } - public void updateActivityReference(MainActivity activity){ - if(extractor==null) return; - extractor.setLoginHelper(activity); + public void updateActivityReference(MainActivity activity) { + if (extractor == null) return; extractor.setDialogueInterface(activity); } - - public void setAnalyzeUICallback(UtilityInterface.AnalyzeUICallback analyzeUICallback) { - this.analyzeUICallback = analyzeUICallback; + public LiveData getFormatsLiveData() { + return formatsLiveData; } + @Override public void onAnalyzeCompleted(Formats formats) { this.formats = formats; - analyzeUICallback.onAnalyzeCompleted(formats.isMultipleFile()); + formatsLiveData.setValue(formats); } + public void removeActivityReference() { + updateActivityReference(null); + } + + public boolean isRecreated() { + return extractor != null && formats == null; + } + MutableLiveData loginDetailsProviderMutableLiveData = new MutableLiveData<>(); + + @Override + public void signInNeeded(UtilityClass.LoginDetailsProvider loginDetailsProvider) { + loginDetailsProviderMutableLiveData.postValue(loginDetailsProvider); + } + + public LiveData getLoginDetailsProviderLiveData() { + return loginDetailsProviderMutableLiveData; + } + + @Override + public String getCookies(int cookiesKey) { + return AppPref.getInstance(getApplication()).getStringValue(cookiesKey, null); + } + + public void clearLoginAlert() { + loginDetailsProviderMutableLiveData.setValue(null); + } } diff --git a/app/src/main/java/com/mugames/vidsnap/utility/DownloadReceiver.java b/app/src/main/java/com/mugames/vidsnap/utility/DownloadReceiver.java index 38a3e9f..b752ad6 100644 --- a/app/src/main/java/com/mugames/vidsnap/utility/DownloadReceiver.java +++ b/app/src/main/java/com/mugames/vidsnap/utility/DownloadReceiver.java @@ -17,6 +17,7 @@ package com.mugames.vidsnap.utility; +import static com.mugames.vidsnap.ui.viewmodels.MainActivityViewModel.downloadDetailsList; import static com.mugames.vidsnap.utility.Statics.ERROR_DOWNLOADING; import static com.mugames.vidsnap.utility.Statics.IS_SHARE_ONLY_DOWNLOAD; import static com.mugames.vidsnap.utility.Statics.OUTFILE_URI; @@ -63,7 +64,6 @@ public class DownloadReceiver extends ResultReceiver implements Parcelable { final String TAG = Statics.TAG + ":DownloadReceiver"; final Context context; - UtilityInterface.DialogueInterface dialogInterface; final MutableLiveData resultBundle = new MutableLiveData<>(); @@ -71,18 +71,13 @@ public class DownloadReceiver extends ResultReceiver implements Parcelable { final int id; DownloadCallback callback; - public DownloadReceiver(Handler handler, Context context, UtilityInterface.DialogueInterface dialogueInterface, int id, DownloadCallback callback) { + public DownloadReceiver(Handler handler, Context context, int id, DownloadCallback callback) { super(handler); this.context = context.getApplicationContext(); this.callback = callback; - this.dialogInterface = dialogueInterface; this.id = id; } - public void setDialogInterface(UtilityInterface.DialogueInterface dialogInterface) { - this.dialogInterface = dialogInterface; - } - public void setCallback(DownloadCallback callback) { this.callback = callback; } @@ -108,9 +103,11 @@ protected void onReceiveResult(int resultCode, Bundle resultData) { if (resultCode == PROGRESS_FAILED) { notificationFailed(resultData); + downloadDetailsList.remove(DownloadDetails.findDetails(id)); return; } if (resultCode == PROGRESS_DONE) { + DownloadDetails downloadDetails = DownloadDetails.findDetails(id); if (!resultData.getBoolean(IS_SHARE_ONLY_DOWNLOAD)) { Uri outputUri = Uri.parse(resultData.getString(OUTFILE_URI, null)); DownloadDetails details = DownloadDetails.findDetails(id); @@ -118,20 +115,26 @@ protected void onReceiveResult(int resultCode, Bundle resultData) { details.fileName = file.getName().split("\\.")[0]; details.fileType = file.getName().split("\\.")[1]; details.fileMime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(details.fileType); - scan(outputUri.toString()); + downloadDetailsList.remove(DownloadDetails.findDetails(id)); + scan(outputUri.toString(),downloadDetails); } else { - callback.onDownloadCompleted(id); - ((MainActivity) dialogInterface).shareDownloadedVideo(resultData); + downloadDetailsList.remove(DownloadDetails.findDetails(id)); + createNullSafeCallback(downloadDetails); } } } - void scan(String fileUri) { - FileUtil.scanMedia(context, fileUri, (s, uri1) -> notificationSetup(uri1)); + void createNullSafeCallback(DownloadDetails downloadDetails) { + if (callback != null) + callback.onDownloadCompleted(downloadDetails); } - void notificationSetup(Uri uri) { - DownloadDetails details = DownloadDetails.findDetails(id); + void scan(String fileUri, DownloadDetails downloadDetails) { + FileUtil.scanMedia(context, fileUri, (s, uri1) -> notificationSetup(uri1,downloadDetails)); + } + + void notificationSetup(Uri uri, DownloadDetails details) { + History history = new History(details, uri); @@ -143,7 +146,12 @@ void notificationSetup(Uri uri) { Intent play_Intent = new Intent(Intent.ACTION_VIEW); play_Intent.setDataAndType(uri, MIMEType.VIDEO_MP4); - PendingIntent pendingIntent = PendingIntent.getActivity(context, 10, play_Intent, 0); + PendingIntent pendingIntent; + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S) { + pendingIntent = PendingIntent.getActivity(context, 10, play_Intent, PendingIntent.FLAG_IMMUTABLE); + } else { + pendingIntent = PendingIntent.getActivity(context, 10, play_Intent, 0); + } builder.setPriority(NotificationCompat.PRIORITY_HIGH) .setContentTitle("Download Completed") .setContentText(details.fileName + "." + details.fileType) @@ -153,7 +161,7 @@ void notificationSetup(Uri uri) { managerCompat.notify(new Random().nextInt(), builder.build()); details.deleteThumbnail(); - callback.onDownloadCompleted(id); + createNullSafeCallback(details); } synchronized void addItemToDB(History history) { @@ -163,7 +171,8 @@ synchronized void addItemToDB(History history) { } void cancelDownload() { - callback.onDownloadCompleted(id); + if (callback!=null) + callback.onDownloadFailed("Canceled",null); } void notificationFailed(Bundle resultData) { @@ -171,9 +180,10 @@ void notificationFailed(Bundle resultData) { details.deleteThumbnail(); String failReason; - if(resultData.getString(ERROR_DOWNLOADING).contains("REQUEST_NOT_SUCCESSFUL")) + if (resultData.getString(ERROR_DOWNLOADING).contains("REQUEST_NOT_SUCCESSFUL")) failReason = "Download Link expired/broken. It cannot be resumed"; - else failReason = "Sorry!! for inconvenience try changing name and re-download or change download location"; + else + failReason = "Sorry!! for inconvenience try changing name and re-download or change download location"; Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(details.srcUrl)); @@ -188,9 +198,8 @@ void notificationFailed(Bundle resultData) { .setAutoCancel(true) .setContentIntent(pendingIntent); managerCompat.notify(new Random().nextInt(), builder.build()); - dialogInterface.error(failReason, - new Exception(resultData.getString(ERROR_DOWNLOADING))); - callback.onDownloadCompleted(id); + if (callback != null) + callback.onDownloadFailed(failReason, new Exception(resultData.getString(ERROR_DOWNLOADING))); } diff --git a/app/src/main/java/com/mugames/vidsnap/utility/JSInterpreter.java b/app/src/main/java/com/mugames/vidsnap/utility/JSInterpreter.java index 1ae3391..bd6afd0 100644 --- a/app/src/main/java/com/mugames/vidsnap/utility/JSInterpreter.java +++ b/app/src/main/java/com/mugames/vidsnap/utility/JSInterpreter.java @@ -24,59 +24,197 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Hashtable; +import java.util.LinkedHashMap; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import static com.mugames.vidsnap.utility.UtilityClass.*; +import android.util.Log; + /** * Used to decrypt the signature by Youtube and only used by {@link com.mugames.vidsnap.extractor.YouTube} */ public class JSInterpreter { - public static final String NAME_REGEX="[a-zA-Z_$][a-zA-Z_$0-9]*"; + public static final String NAME_REGEX = "[a-zA-Z_$][a-zA-Z_$0-9]*"; public String code; - public Hashtable> objects; + public Hashtable> objects; public Hashtable functions; - Hashtable local_vars = new Hashtable<>(); - + LinkedHashMap localVars = new LinkedHashMap<>(); - - public JSInterpreter(String code, Hashtable> objects) { + public JSInterpreter(String code, Hashtable> objects) { this.code = code; - if(objects==null) this.objects = new Hashtable<>(); - else this.objects=objects; - this.functions=new Hashtable<>(); + if (objects == null) this.objects = new Hashtable<>(); + else this.objects = objects; + this.functions = new Hashtable<>(); } - public JSInterface Extract_Function(String functionName) { + public JSInterface extractFunction(String functionName) { Pattern pattern = Pattern.compile(String.format("(?:function\\s+%s|[{;,]\\s*%s\\s*=\\s*function|var\\s+%s\\s*=\\s*function)\\s*\\(([^)]*)\\)\\s*\\{([^}]+)\\}" , functionName, functionName, functionName)); Matcher func_m = pattern.matcher(code); if (func_m.find()) { String[] argnames = func_m.group(1).split(","); - return Build_Function(argnames, func_m.group(2)); + return buildFunction(argnames, func_m.group(2)); } return null; } - JSInterface Build_Function(String[] argnames, String function_code){ + public JSFunctionCode extractFunctionCode(String functionName) { + Matcher matcher = Pattern.compile(String.format( + "function\\s+%s\\s*\\(([^)]*)\\)\\s*(\\{(?:(?!\\};)[^\"]|\"([^\"]|\\\\\")*\")+\\})", + functionName + )).matcher(code); + if (!matcher.find()) { + matcher = Pattern.compile(String.format( + "[{;,]\\s*%s\\s*=\\s*function\\s*\\((.*?)\\)\\s*(\\{[\\w\\W]*?\\};)", + functionName + )).matcher(code); + if (!matcher.find()) { + matcher = Pattern.compile(String.format( + "var\\s+%s\\s*=\\s*function\\s*\\(([^)]*)\\)\\s*(\\{(?:(?!};)[^\"]|\"([^\"]|\\\\\")*\")+\\})", + functionName + )).matcher(code); + if (!matcher.find()) { + Log.w("JSInterpreter", "extractFunctionCode: not found Download will be throttled"); + return null; + } + } + } + ArrayList separate = separateAtParen(matcher.group(2), "}"); + if (separate.size() >= 2) { + String args = matcher.group(1); + String[] argsArray = null; + if (args != null) { + argsArray = args.split(","); + } + return new JSFunctionCode(argsArray, separate.get(0)); + } else return new JSFunctionCode(separate.get(0)); + } + + + public JSInterface extractFunctionFromCode(JSFunctionCode functionCode) { + String jsCode = functionCode.code; + while (true) { + Matcher mobj = Pattern.compile("function\\(([^)]*)\\)\\s*\\{") + .matcher(jsCode); + if (!mobj.find()) + break; + int start = mobj.start(); + int bodyStart = mobj.end(); + ArrayList separated = separateAtParen(jsCode.substring(bodyStart - 1), "}"); + String body = separated.get(0); + String remaining = separated.get(1); + Log.d("JSIP", "extractFunctionFromCode: " + code.substring(0, start)); + String name = namedObject( + extractFunctionFromCode(new JSFunctionCode(mobj.group(1).split(","), body)) + ); + jsCode = jsCode.substring(0, start) + name + remaining; + } + return buildFunction(functionCode.args, jsCode); + } + + int nameObjectCounter = 0; + + String namedObject(Object obj) { + nameObjectCounter++; + String name = String.format("vidsnap_interp_obj%s", nameObjectCounter); + localVars.put(name, obj); + return name; + } + + ArrayList separateAtParen(String exp, String delimiter) { + ArrayList separated = separate(exp, delimiter, 1); + if (separated.size() < 2) + throw new IllegalArgumentException(String.format("No terminating paren %s in %s", delimiter, exp)); + return new ArrayList<>(Arrays.asList( + separated.get(0).substring(1), separated.get(1).trim() + )); + } + + ArrayList separate(String exp, String delimiter, int maxSplit) { + ArrayList matchingParen = new ArrayList<>(Arrays.asList( + "(", "{", "[" + )); + LinkedHashMap counter = new LinkedHashMap<>(); + counter.put(")", 0); + counter.put("}", 0); + counter.put("]", 0); + + int start = 0; + int splits = 0; + int pos = 0; + int delimiterLength = delimiter.length() - 1; + + ArrayList stringArrayList = new ArrayList<>(); + + int idx = -1; + for (char chr : exp.toCharArray()) { + idx++; + String ch = String.valueOf(chr); + if (matchingParen.contains(ch)) { + int index = matchingParen.indexOf(ch); + String key = String.valueOf(counter.keySet().toArray()[index]); + counter.put( + key, + counter.get(key) + 1 + ); + } else if (counter.containsKey(ch)) { + counter.put( + ch, + counter.get(ch) - 1 + ); + } + if (!ch.equals(String.valueOf(delimiter.charAt(pos))) || any(counter.values())) { + pos = 0; + continue; + } else if (pos != delimiterLength) { + pos += 1; + continue; + } + stringArrayList.add(exp.substring(start, idx - delimiterLength)); + start = idx + 1; + pos = 0; + splits++; + if (maxSplit >= 0 && splits >= maxSplit) break; + } + stringArrayList.add(exp.substring(start)); + return stringArrayList; + } + + boolean any(Collection collection) { + if (collection.isEmpty()) return false; + int zeroCount = 0; + for (T item : collection) { + if (item instanceof Boolean) { + if (!(Boolean) item) return false; + } else if (item instanceof Number) { + if (item.equals(0)) { + zeroCount++; + } + } + } + return zeroCount != collection.size(); + } + + JSInterface buildFunction(String[] argnames, String function_code) { return new JSInterface() { @Override public Object resf(Object[] values) { - - for (int i=0;i subStatements = separate(stmt, ";", -1); + stmt = subStatements.get(subStatements.size() - 1); + subStatements.remove(subStatements.size() - 1); + for (String subStmt : subStatements) { + JSResultArray array = interpretStatement(subStmt, recurLimit - 1); + if (array.abort) return array; + } + resultArray.abort = false; + stmt = stmt.replaceAll("^\\s+", ""); + Pattern pattern = Pattern.compile("var\\s"); + Matcher stmt_m = pattern.matcher(stmt); + if (stmt_m.find()) expr = stmt.substring(stmt_m.group(0).length()); + else { + Pattern return_p = Pattern.compile("return(?:\\s+|$)"); + Matcher return_m = return_p.matcher(stmt); + if (return_m.find()) { + expr = stmt.substring(return_m.group(0).length()); + resultArray.abort = true; + } else { + expr = stmt; } } - resultArray.res= Interpret_Expression(expr,recurLimit); + resultArray.res = interpretExpression(expr, recurLimit); return resultArray; } - Object Interpret_Expression(String expr,int recurLimit){ + Object interpretExpression(String expr, int recurLimit) { Object obj = new Hashtable<>(); Hashtable obj_m = new Hashtable<>(); - List arg_val = new ArrayList<>(); + List argVal = new ArrayList<>(); expr = expr.trim(); - if(expr.isEmpty()) + if (expr.isEmpty()) return null; - if(expr.startsWith("(")){ - int parens_Count=0; - Pattern pattern =Pattern.compile("[()]"); - Matcher matcher=pattern.matcher(expr); - while (matcher.find()){ - if(matcher.group(0).equals("(")) parens_Count++; + if (expr.startsWith("{")) { + ArrayList innerOuter = separateAtParen(expr, "}"); + JSResultArray resultArray = interpretStatement(innerOuter.get(0), recurLimit - 1); + String outer = null; + try { + outer = innerOuter.get(1); + } catch (ArrayIndexOutOfBoundsException e) { + } + if (outer != null || resultArray.abort) return innerOuter.get(0); + else { + + } + + } + if (expr.startsWith("(")) { + int parens_Count = 0; + Pattern pattern = Pattern.compile("[()]"); + Matcher matcher = pattern.matcher(expr); + while (matcher.find()) { + if (matcher.group(0).equals("(")) parens_Count++; else { - parens_Count-=1; - if(parens_Count==0){ - String sub_expr=expr.substring(1,matcher.start()); - String sub_res= (String) Interpret_Expression(sub_expr,recurLimit); - String remaining_expr=expr.substring(matcher.end()).trim(); - if(remaining_expr.isEmpty()) return sub_res; - else expr= sub_res+remaining_expr; + parens_Count -= 1; + if (parens_Count == 0) { + String sub_expr = expr.substring(1, matcher.start()); + String sub_res = (String) interpretExpression(sub_expr, recurLimit); + String remaining_expr = expr.substring(matcher.end()).trim(); + if (remaining_expr.isEmpty()) return sub_res; + else expr = sub_res + remaining_expr; break; } //else {/*wrong JS code*/} @@ -143,198 +300,494 @@ Object Interpret_Expression(String expr,int recurLimit){ } } } - for (String s : Operator.assign_operators){ - Pattern p=Pattern.compile(String.format("(%s)(?:\\[([^\\]]+?)\\])?\\s*\\%s(.*)$",NAME_REGEX,s)); + if (expr.startsWith("[")) { + ArrayList innerOuter = separateAtParen(expr, "]"); + for (String item : separate(innerOuter.get(0), ",", -1)) { + String name = namedObject(interpretExpression(item, recurLimit)); + expr = name + innerOuter.get(1); + } + } + Matcher matcher = Pattern.compile("try\\s*").matcher(expr); + if (matcher.find()) { + ArrayList arrayList; + if (expr.charAt(matcher.end()) == '{') { + arrayList = separateAtParen(expr.substring(matcher.end()), "}"); + } else { + arrayList = new ArrayList<>(Arrays.asList(expr.substring(matcher.end()), "")); + } + String tryExpr = arrayList.get(0); + expr = arrayList.get(1); + JSResultArray resultArray = interpretStatement(tryExpr, recurLimit - 1); + if (resultArray.abort) return resultArray; + return interpretStatement(expr, recurLimit - 1); + } + matcher = Pattern.compile("(?:(catch)|(for)|(switch))\\s*\\(").matcher(expr); + if (matcher.find()) { + if (matcher.group(1) != null) { + //Skip catch block + ArrayList list = separateAtParen(expr, "}"); + return interpretStatement(list.get(1), recurLimit - 1); + } else if (matcher.group(2) != null) { + //For loop + ArrayList stringArrayList = separateAtParen(expr.substring(matcher.end() - 1), ")"); + String constructor = stringArrayList.get(0); + String remaining = stringArrayList.get(1); + String body; + if (remaining.startsWith("{")) { + ArrayList list = separateAtParen(remaining, "}"); + expr = list.get(1); + body = list.get(0); + } else { + matcher = Pattern.compile("switch\\s*\\(").matcher(remaining); + if (matcher.find()) { + ArrayList list = separateAtParen(remaining.substring(matcher.end() - 1), ")"); + String switchVal = list.get(0); + remaining = list.get(1); + list = separateAtParen(remaining, "}"); + body = list.get(0); + expr = list.get(1); + body = String.format("switch(%s){%s}", switchVal, body); + } else { + body = remaining; + expr = ""; + } + ArrayList strings = separate(constructor, ";", -1); + String start = strings.get(0); + String cndn = strings.get(1); + String increment = strings.get(2); + if (interpretStatement(start, recurLimit - 1).abort) + throw new IllegalStateException("Premature return in the initialization of a for loop in " + constructor); + while (true) { + if (!interpretStatement(cndn, recurLimit - 1).abort) break; + try { + JSResultArray resultArray = interpretStatement(body, recurLimit - 1); + if (resultArray.abort) return resultArray; + } catch (JSBreak jsBreak) { + break; + } catch (JSContinue jsContinue) { + } + if (interpretStatement(increment, recurLimit - 1).abort) + throw new IllegalStateException("Premature return in the initialization of a for loop in " + constructor); + return interpretStatement(expr, recurLimit - 1); + } + } + } else if (matcher.group(3) != null) { + //switch block + ArrayList stringArrayList = separateAtParen(expr.substring(matcher.end() - 1), ")"); + String switchVal = stringArrayList.get(0); + String remaining = stringArrayList.get(1); + switchVal = String.valueOf(interpretExpression(switchVal, recurLimit)); + stringArrayList = separateAtParen(remaining, "}"); + String body = stringArrayList.get(0); + expr = stringArrayList.get(1); + String[] items = body.replaceAll("default:", "case default:").split("case "); + for (boolean def : new boolean[]{false, true}) { + boolean matched = false; + for (int i = 1; i < items.length; i++) { + String cas = null; + String stmt = null; + ArrayList tempsList = separate(items[i], ":", 1); + for (int j = 0; j < tempsList.size(); j++) { + if (j == 0) { + cas = tempsList.get(j).trim(); + } else { + stmt = tempsList.get(j).trim(); + } + } + if (def) { + matched = matched || cas.equals("default"); + } else if (!matched) { + matched = !cas.equals("default") && + switchVal.equals(interpretExpression(cas, recurLimit)); + } + if (!matched) + continue; + try { + JSResultArray jsResultArray = interpretStatement(stmt, recurLimit - 1); + if (jsResultArray.abort) return jsResultArray; + } catch (JSBreak e) { + break; + } + } + if (matched) break; + } + return interpretStatement(expr, recurLimit - 1); + } + } + //Comma separated statements + ArrayList subExpression = separate(expr, ",", -1); + try { + expr = subExpression.get(subExpression.size() - 1).trim(); + subExpression.remove(subExpression.size() - 1); + } catch (ArrayIndexOutOfBoundsException e) { + expr = ""; + } + for (String subExpr : subExpression) { + interpretExpression(subExpr, recurLimit); + } + + matcher = Pattern.compile("(\\\\+\\\\+|--)([a-zA-Z_$][a-zA-Z_$0-9]*)|([a-zA-Z_$][a-zA-Z_$0-9]*)(\\\\+\\\\+|--)").matcher(expr); + while (matcher.find()) { + String var = matcher.group(2); + if (var == null) var = matcher.group(3); + int start = matcher.start(); + int end = matcher.end(); + String sign = matcher.group(1); + if (sign == null) sign = matcher.group(4); + String ret = String.valueOf(localVars.get(var)); + expr = expr.substring(0, start) + ret + expr.substring(end); + } + + for (String s : Operator.assign_operators) { + Pattern p = Pattern.compile(String.format("(%s)(?:\\[([^\\]]+?)\\])?\\s*\\%s(.*)$", NAME_REGEX, s)); Matcher m = p.matcher(expr); - if(!m.find()) continue; + if (!m.find()) continue; char[] right_val; - Object o = Interpret_Expression(m.group(3),recurLimit-1); - try{ - right_val= stringToCharArray((String)o); + Object o = interpretExpression(m.group(3), recurLimit - 1); + try { + right_val = stringToCharArray((String) o); } catch (Exception e) { - try{ - right_val= stringToCharArray(charArrayToSting((char[])o)); + try { + right_val = stringToCharArray(charArrayToSting((char[]) o)); - } catch (Exception exception) { - right_val=new char[1]; - right_val[0]=(char) o; + } catch (Exception exception) { + right_val = new char[1]; + right_val[0] = (char) o; } } - if(m.group(2)!=null){ - char[] lvar= stringToCharArray(charArrayToSting((char[]) local_vars.get(m.group(1)))); - int idx= (int) Interpret_Expression(m.group(2),recurLimit); + if (m.group(2) != null) { + char[] lvar = stringToCharArray(charArrayToSting((char[]) localVars.get(m.group(1)))); + int idx = (int) interpretExpression(m.group(2), recurLimit); assert lvar != null; - String cur=String.valueOf(lvar[idx]); + String cur = String.valueOf(lvar[idx]); - String val=Operator.Operation(s,cur, Arrays.toString(right_val)); + String val = Operator.Operation(s, cur, Arrays.toString(right_val)); assert val != null; lvar[idx] = val.toCharArray()[1]; - local_vars.put(m.group(1),lvar); + localVars.put(m.group(1), lvar); return val; - } - else { - String cur= String.valueOf(local_vars.get(m.group(1))); - char[] val= stringToCharArray(Operator.Operation(s,cur, charArrayToSting(right_val))); - local_vars.put(m.group(1),val); + } else { + String cur = String.valueOf(localVars.get(m.group(1))); + char[] val = stringToCharArray(Operator.Operation(s, cur, charArrayToSting(right_val))); + localVars.put(m.group(1), val); return val; } } - if(Character.isDigit(expr.charAt(0))) return Integer.parseInt(expr); + if (Character.isDigit(expr.charAt(0))) return Integer.parseInt(expr); - Pattern var_pattern=Pattern.compile(String.format("(?!if|return|true|false)(%s)$",NAME_REGEX)); - Matcher var_m=var_pattern.matcher(expr); + if (expr.equals("break")) throw new JSBreak(); + else if (expr.equals("continue")) throw new JSContinue(); - if(var_m.find() && var_m.matches()) { - if(local_vars.containsKey(var_m.group(1))) - return local_vars.get(var_m.group(1)); + Pattern var_pattern = Pattern.compile(String.format("(?!if|return|true|false)(%s)$", NAME_REGEX)); + Matcher var_m = var_pattern.matcher(expr); + + if (var_m.find() && var_m.matches()) { + if (localVars.containsKey(var_m.group(1))) + return localVars.get(var_m.group(1)); } - try{ - if(expr.equals("''")||expr.equals("\"\""))return "\"\""; + try { + if (expr.equals("''") || expr.equals("\"\"")) return "\"\""; return new JSONObject(expr); } catch (JSONException e) { } - Pattern m_p=Pattern.compile(String.format("(%s)\\[(.+)\\]$",NAME_REGEX)); - Matcher m=m_p.matcher(expr); - if(m.find()){ - char[] val= (char[]) local_vars.get(m.group(1)); - val= stringToCharArray(charArrayToSting(val)); - int idx= (int) Interpret_Expression(m.group(2),recurLimit-1); + Pattern m_p = Pattern.compile(String.format("(%s)\\[(.+)\\]$", NAME_REGEX)); + Matcher m = m_p.matcher(expr); + if (m.find()) { + char[] val = (char[]) localVars.get(m.group(1)); + val = stringToCharArray(charArrayToSting(val)); + int idx = (int) interpretExpression(m.group(2), recurLimit - 1); return val[idx]; } - m_p=Pattern.compile(String.format("(%s)(?:\\.([^(]+)|\\[([^]]+)\\])\\s*(?:\\(+([^()]*)\\))?$",NAME_REGEX)); - m=m_p.matcher(expr); - if(m.find()&& m.matches()){ - String variable=m.group(1); - String member= removeQuotes(m.group(2)); - if (member.isEmpty()) member = removeQuotes(m.group(3)); - String arg_str=m.group(4); - if(local_vars.containsKey(variable)) obj = local_vars.get(variable); - else{ - if (!objects.contains(variable)){ - objects.put(variable,Extract_Object(variable)); - } - obj_m=objects.get(variable); - } - - if(arg_str==null){ - if(member.equals("length")){ - assert obj != null; - int x=obj_m.size(); - if (x==0) x = stringToCharArray(charArrayToSting((char[])obj)).length; - return x; - } - return (obj_m.get(member)); - } - assert expr.endsWith(")"); - if(arg_str.isEmpty()){ - arg_val=new ArrayList<>(); - } - else { - for (String v : arg_str.split(",")){ - arg_val.add(Interpret_Expression(v,recurLimit)); - } - } - if(member.equals("split")){ - assert obj != null; - return (obj); - } - if(member.equals("join")){ - char[] va= joinArray(stringToCharArray((String) arg_val.get(0)),(char[]) obj); - local_vars.put(variable,va); - return va; - } - if(member.equals("reverse")){ - char[] va= reverse(stringToCharArray( charArrayToSting((char[]) obj))); - local_vars.put(variable,va); - return va; - } - if(member.equals("slice")){ - Object va =((String)obj).substring(Integer.parseInt(String.valueOf((arg_val.get(0))))); - local_vars.put(variable,va); - return va; - } - if(member.equals("splice")){ - int index= Integer.parseInt((arg_val.get(0)+"")); - int howMany= Integer.parseInt((arg_val.get(1)+"")); - char[] t= (char[]) obj; - String t_s= charArrayToSting(t); - int isize=Math.min(index+howMany,t.length); - char[] res=new char[isize]; - for(int i=index;i list = separateAtParen(arg_str, ")"); + arg_str = list.get(0); + remaining = list.get(1); + } else { + remaining = arg_str; + arg_str = null; + } + if (remaining != null) { + return interpretExpression(namedObject(evalMethod(member,variable,arg_str,recurLimit)) + remaining, recurLimit); + } else { + return evalMethod(member,variable,arg_str,recurLimit); + } + +// if (localVars.containsKey(variable)) obj = localVars.get(variable); +// else { +// if (!objects.contains(variable)) { +// objects.put(variable, extractObject(variable)); +// } +// obj_m = objects.get(variable); +// } +// +// if (arg_str == null) { +// if (member.equals("length")) { +// assert obj != null; +// int x = obj_m.size(); +// if (x == 0) x = stringToCharArray(charArrayToSting((char[]) obj)).length; +// return x; +// } +// return (obj_m.get(member)); +// } +// assert expr.endsWith(")"); +// if (arg_str.isEmpty()) { +// argVal = new ArrayList<>(); +// } else { +// for (String v : arg_str.split(",")) { +// argVal.add(interpretExpression(v, recurLimit)); +// } +// } +// if (member.equals("split")) { +// assert obj != null; +// return (obj); +// } +// if (member.equals("join")) { +// char[] va = joinArray(stringToCharArray((String) argVal.get(0)), (char[]) obj); +// localVars.put(variable, va); +// return va; +// } +// if (member.equals("reverse")) { +// char[] va = reverse(stringToCharArray(charArrayToSting((char[]) obj))); +// localVars.put(variable, va); +// return va; +// } +// if (member.equals("slice")) { +// Object va = ((String) obj).substring(Integer.parseInt(String.valueOf((argVal.get(0))))); +// localVars.put(variable, va); +// return va; +// } +// if (member.equals("splice")) { +// int index = Integer.parseInt((argVal.get(0) + "")); +// int howMany = Integer.parseInt((argVal.get(1) + "")); +// char[] t = (char[]) obj; +// String t_s = charArrayToSting(t); +// int isize = Math.min(index + howMany, t.length); +// char[] res = new char[isize]; +// for (int i = index; i < isize; i++) { +// res[i] = t_s.charAt(i); +// obj = popCharArray((char[]) obj, index); +// } +// localVars.put(variable, obj); +// return res; +// } +// if (member.equals("unshift")) { +// if (obj instanceof Collection) +// throw new IllegalArgumentException("OBJ must be type of list"); +// if (argVal.isEmpty()) +// throw new IllegalArgumentException("Argument values are null"); +// char[] temp = new char[argVal.size()]; +// char[] tempObj = new char[((Collection) obj).size()]; +// for (int i = 0; i < argVal.size(); i++) { +// temp[i] = (char) argVal.get(i); +// } +// for (char item : reverse(temp)) { +// //TODO line 434 +// } +// } else if (member.equals("pop")) { +// if (obj instanceof Collection) +// throw new IllegalArgumentException("OBJ must be type of list"); +// if (argVal.isEmpty()) +// throw new IllegalArgumentException("Argument values are null"); +// //TODO 441 +// } else if (member.equals("push")) { +// +// } else if (member.equals("forEach")) { +// +// } else if (member.equals("indexOf")) { +// +// } +// JSInterface anInterface = obj_m.get(member); +// return anInterface.resf(argVal.toArray()); } - m_p=Pattern.compile(String.format("^(%s)\\(([a-zA-Z0-9_$,]*)\\)$",NAME_REGEX)); - m=m_p.matcher(expr); - if(m.find()) { + m_p = Pattern.compile(String.format("^(%s)\\(([a-zA-Z0-9_$,]*)\\)$", NAME_REGEX)); + m = m_p.matcher(expr); + if (m.find()) { String funcName = m.group(1); if (m.group(2).length() > 0) { for (String v : m.group(2).split(",")) { if (Character.isDigit(v.charAt(0))) { - if(!functions.contains(funcName)) - functions.put(funcName,Extract_Function(funcName)); - return functions.get(funcName).resf(arg_val.toArray()); + if (!functions.contains(funcName)) + functions.put(funcName, extractFunction(funcName)); + return functions.get(funcName).resf(argVal.toArray()); + } else { } - else {} } } - }else { + } else { } return null; } - private Hashtable Extract_Object(String variable) { - String FUNCTION_NAME="(?:[a-zA-Z$0-9]+|\"[a-zA-Z$0-9]+\"|'[a-zA-Z$0-9]+')"; + Object evalMethod(String member, String variable, String argStr, int recurLimit) { + Object obj; + if (variable.equals("String")) obj = ""; + else if (localVars.containsKey(variable)) obj = localVars.get(variable); + else obj = extractObject(variable);//Line 380 + + if (argStr == null) { + if (member.equals("length")) return ((String) obj).length(); + } + ArrayList argVals = new ArrayList<>(); + for (String v : separate(argStr, ",", -1)) { + argVals.add(interpretExpression(v, recurLimit)); + } + if (obj instanceof String) { + if (member.equals("fromCharCode")) { + StringBuilder val = new StringBuilder(); + for (Object o : argVals) { + val.append((char) ((int) o)); + } + return val.toString(); + } + } + + if (member.equals("split")) { + return UtilityClass.stringToCharArray((String) obj); + } else if (member.equals("join")) { + return joinArray(stringToCharArray((String) argVals.get(0)), (char[]) obj); + } else if (member.equals("reverse")) { + return reverse(stringToCharArray(charArrayToSting((char[]) obj))); + } else if (member.equals("slice")) { + + } else if (member.equals("splice")) { + ArrayList objList = convertCharArrayToCharacterList((char[]) obj); + argVals.add(objList.size()); + int index = Integer.parseInt(String.valueOf(argVals.get(0))); + int howMany = Integer.parseInt(String.valueOf(argVals.get(1))); + argVals.remove(argVals.size() - 1); + if (index < 0) index += objList.size(); + ArrayList addItems = new ArrayList<>(); + for (int i = 2; i < argVals.size(); i++) { + addItems.add(Integer.parseInt(String.valueOf(argVals.get(i)))); + } + + ArrayList res = new ArrayList<>(); + for (int i = index; i < Math.min(index + howMany, objList.size()); i++) { + res.add(objList.get(objList.size() - 1)); + objList.remove(objList.size() - 1); + } + for (int i = 0; i < addItems.size(); i++) { +// objList.add(index+i,); //Line 428 + } + return convertObjectListToCharArray(res); + } else if (member.equals("unshift")) { + ArrayList tempObj = convertCharArrayToCharacterList((char[]) obj); + for (char c : reverse(convertObjectListToCharArray(argVals))) { + tempObj.add(0, c); + } + return tempObj.toArray(); + } else if (member.equals("pop")) { + if (obj == null) return null; + String v = charArrayToSting((char[]) obj); + return stringToCharArray(v.substring(0, v.length() - 1)); + } else if (member.equals("push")) { + ArrayList objList = convertCharArrayToCharacterList((char[]) obj); + ArrayList tempList = new ArrayList<>(objList); + for (Object o : argVals) { + tempList.add((char) o); + } + return convertObjectListToCharArray(tempList); + } else if (member.equals("forEach")) { + JSInterface f = (JSInterface) argVals.get(0); + ArrayList objects = new ArrayList<>(); + char[] objArray = ((char[]) obj); + for (int i = 0; i < objArray.length; i++) { + objects.add((char[]) f.resf(new Object[]{objArray[i], i, objArray})); + } + return convertObjectListToCharArray(objects); + } else if (member.equals("indexOf")) { + char idx = (char) argVals.get(0); + int start = 0; + try { + start = Integer.parseInt(String.valueOf(argVals.get(1))); + } catch (ArrayIndexOutOfBoundsException e) { + } + + int index = -1; + char[] objArray = (char[]) obj; + for (int i = start; i < objArray.length; i++) { + if (objArray[i] == idx) index = i; + } + return index; + + } + return null; + } + + private Hashtable extractObject(String variable) { + String FUNCTION_NAME = "(?:[a-zA-Z$0-9]+|\"[a-zA-Z$0-9]+\"|'[a-zA-Z$0-9]+')"; Hashtable obj = new Hashtable<>(); - Pattern obj_p=Pattern.compile(String.format("(?. + * + */ + +package com.mugames.vidsnap.utility; + +import com.mugames.vidsnap.utility.UtilityInterface.JSInterface; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Hashtable; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static com.mugames.vidsnap.utility.UtilityClass.*; + +/** + * @author Udhaya + * Created on 28-02-2022 + */ + +public class JSInterpreterLegacy { + public static final String NAME_REGEX="[a-zA-Z_$][a-zA-Z_$0-9]*"; + + public String code; + public Hashtable> objects; + public Hashtable functions; + Hashtable local_vars = new Hashtable<>(); + + + + + public JSInterpreterLegacy(String code, Hashtable> objects) { + this.code = code; + if(objects==null) this.objects = new Hashtable<>(); + else this.objects=objects; + this.functions=new Hashtable<>(); + + } + + public JSInterface extractFunction(String functionName) { + Pattern pattern = Pattern.compile(String.format("(?:function\\s+%s|[{;,]\\s*%s\\s*=\\s*function|var\\s+%s\\s*=\\s*function)\\s*\\(([^)]*)\\)\\s*\\{([^}]+)\\}" + , functionName, functionName, functionName)); + Matcher func_m = pattern.matcher(code); + if (func_m.find()) { + String[] argnames = func_m.group(1).split(","); + return buildFunction(argnames, func_m.group(2)); + } + return null; + } + JSInterface buildFunction(String[] argnames, String function_code){ + + return new JSInterface() { + @Override + public Object resf(Object[] values) { + + for (int i=0;i(); + Hashtable obj_m = new Hashtable<>(); + List arg_val = new ArrayList<>(); + expr = expr.trim(); + if(expr.isEmpty()) + return null; + if(expr.startsWith("(")){ + int parens_Count=0; + Pattern pattern =Pattern.compile("[()]"); + Matcher matcher=pattern.matcher(expr); + while (matcher.find()){ + if(matcher.group(0).equals("(")) parens_Count++; + else { + parens_Count-=1; + if(parens_Count==0){ + String sub_expr=expr.substring(1,matcher.start()); + String sub_res= (String) interpretExpression(sub_expr,recurLimit); + String remaining_expr=expr.substring(matcher.end()).trim(); + if(remaining_expr.isEmpty()) return sub_res; + else expr= sub_res+remaining_expr; + break; + } + //else {/*wrong JS code*/} + + } + } + } + for (String s : Operator.assign_operators){ + Pattern p=Pattern.compile(String.format("(%s)(?:\\[([^\\]]+?)\\])?\\s*\\%s(.*)$",NAME_REGEX,s)); + Matcher m = p.matcher(expr); + if(!m.find()) continue; + char[] right_val; + Object o = interpretExpression(m.group(3),recurLimit-1); + try{ + right_val= stringToCharArray((String)o); + } catch (Exception e) { + try{ + right_val= stringToCharArray(charArrayToSting((char[])o)); + + } catch (Exception exception) { + right_val=new char[1]; + right_val[0]=(char) o; + } + } + + if(m.group(2)!=null){ + char[] lvar= stringToCharArray(charArrayToSting((char[]) local_vars.get(m.group(1)))); + int idx= (int) interpretExpression(m.group(2),recurLimit); + assert lvar != null; + String cur=String.valueOf(lvar[idx]); + + String val=Operator.Operation(s,cur, Arrays.toString(right_val)); + + assert val != null; + lvar[idx] = val.toCharArray()[1]; + local_vars.put(m.group(1),lvar); + return val; + } + else { + String cur= String.valueOf(local_vars.get(m.group(1))); + char[] val= stringToCharArray(Operator.Operation(s,cur, charArrayToSting(right_val))); + local_vars.put(m.group(1),val); + return val; + } + } + + if(Character.isDigit(expr.charAt(0))) return Integer.parseInt(expr); + + Pattern var_pattern=Pattern.compile(String.format("(?!if|return|true|false)(%s)$",NAME_REGEX)); + Matcher var_m=var_pattern.matcher(expr); + + if(var_m.find() && var_m.matches()) { + if(local_vars.containsKey(var_m.group(1))) + return local_vars.get(var_m.group(1)); + } + try{ + if(expr.equals("''")||expr.equals("\"\""))return "\"\""; + return new JSONObject(expr); + } catch (JSONException e) { + } + Pattern m_p=Pattern.compile(String.format("(%s)\\[(.+)\\]$",NAME_REGEX)); + Matcher m=m_p.matcher(expr); + if(m.find()){ + char[] val= (char[]) local_vars.get(m.group(1)); + val= stringToCharArray(charArrayToSting(val)); + int idx= (int) interpretExpression(m.group(2),recurLimit-1); + return val[idx]; + } + + m_p=Pattern.compile(String.format("(%s)(?:\\.([^(]+)|\\[([^]]+)\\])\\s*(?:\\(+([^()]*)\\))?$",NAME_REGEX)); + m=m_p.matcher(expr); + if(m.find()&& m.matches()){ + String variable=m.group(1); + String member= removeQuotes(m.group(2)); + if (member.isEmpty()) member = removeQuotes(m.group(3)); + String arg_str=m.group(4); + if(local_vars.containsKey(variable)) obj = local_vars.get(variable); + else{ + if (!objects.contains(variable)){ + objects.put(variable, extractObject(variable)); + } + obj_m=objects.get(variable); + } + + if(arg_str==null){ + if(member.equals("length")){ + assert obj != null; + int x=obj_m.size(); + if (x==0) x = stringToCharArray(charArrayToSting((char[])obj)).length; + return x; + } + return (obj_m.get(member)); + } + assert expr.endsWith(")"); + if(arg_str.isEmpty()){ + arg_val=new ArrayList<>(); + } + else { + for (String v : arg_str.split(",")){ + arg_val.add(interpretExpression(v,recurLimit)); + } + } + if(member.equals("split")){ + assert obj != null; + return (obj); + } + if(member.equals("join")){ + char[] va= joinArray(stringToCharArray((String) arg_val.get(0)),(char[]) obj); + local_vars.put(variable,va); + return va; + } + if(member.equals("reverse")){ + char[] va= reverse(stringToCharArray( charArrayToSting((char[]) obj))); + local_vars.put(variable,va); + return va; + } + if(member.equals("slice")){ + Object va =((String)obj).substring(Integer.parseInt(String.valueOf((arg_val.get(0))))); + local_vars.put(variable,va); + return va; + } + if(member.equals("splice")){ + int index= Integer.parseInt((arg_val.get(0)+"")); + int howMany= Integer.parseInt((arg_val.get(1)+"")); + char[] t= (char[]) obj; + String t_s= charArrayToSting(t); + int isize=Math.min(index+howMany,t.length); + char[] res=new char[isize]; + for(int i=index;i 0) { + for (String v : m.group(2).split(",")) { + if (Character.isDigit(v.charAt(0))) { + if(!functions.contains(funcName)) + functions.put(funcName, extractFunction(funcName)); + return functions.get(funcName).resf(arg_val.toArray()); + } + else {} + } + } + }else { + + } + + return null; + } + + private Hashtable extractObject(String variable) { + String FUNCTION_NAME="(?:[a-zA-Z$0-9]+|\"[a-zA-Z$0-9]+\"|'[a-zA-Z$0-9]+')"; + Hashtable obj = new Hashtable<>(); + Pattern obj_p=Pattern.compile(String.format("(? chars.length) return chars; char[] x = new char[chars.length - 1]; System.arraycopy(chars, 0, x, 0, index); @@ -136,7 +139,7 @@ public static char[] popCharArray(char[] chars, int index) { return x; } - public static char[] stringToCharArray(String s) { + public static synchronized char[] stringToCharArray(String s) { char[] chars = new char[s.length()]; for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); @@ -148,7 +151,7 @@ public static char[] stringToCharArray(String s) { return chars; } - public static char[] reverse(char[] a) { + public synchronized static char[] reverse(char[] a) { char[] b = new char[a.length]; int j = a.length; for (int i = 0; i < a.length; i++, j--) { @@ -157,6 +160,21 @@ public static char[] reverse(char[] a) { return b; } + public synchronized static ArrayList convertCharArrayToCharacterList(char[] array) { + ArrayList list = new ArrayList<>(); + for (char c : array) { + list.add(c); + } + return list; + } + + public synchronized static char[] convertObjectListToCharArray(ArrayList list){ + char[] chars = new char[list.size()]; + for (int i = 0; i < list.size(); i++) { + chars[i] = (char) list.get(i); + } + return chars; + } private static String Escape_regex(String fileName) { StringBuilder builder = new StringBuilder(); @@ -309,5 +327,39 @@ public static JSONArray getArray_or_Null(JSONObject object, String name) { } + public static class LoginDetailsProvider{ + String reason; + String loginURL; + String[] loginDoneUrl; + int cookiesKey; + UtilityInterface.LoginIdentifier identifier; + + public LoginDetailsProvider(String reason, String loginURL, String[] loginDoneUrl, int cookiesKey, UtilityInterface.LoginIdentifier identifier) { + this.reason = reason; + this.loginURL = loginURL; + this.loginDoneUrl = loginDoneUrl; + this.cookiesKey = cookiesKey; + this.identifier = identifier; + } + + public String getReason() { + return reason; + } + public String getLoginURL() { + return loginURL; + } + + public String[] getLoginDoneUrl() { + return loginDoneUrl; + } + + public int getCookiesKey() { + return cookiesKey; + } + + public UtilityInterface.LoginIdentifier getIdentifier() { + return identifier; + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/mugames/vidsnap/utility/UtilityInterface.java b/app/src/main/java/com/mugames/vidsnap/utility/UtilityInterface.java index a2f0b22..090eeb8 100644 --- a/app/src/main/java/com/mugames/vidsnap/utility/UtilityInterface.java +++ b/app/src/main/java/com/mugames/vidsnap/utility/UtilityInterface.java @@ -22,30 +22,20 @@ import android.view.MotionEvent; import com.mugames.vidsnap.network.Response; +import com.mugames.vidsnap.utility.bundles.DownloadDetails; import com.mugames.vidsnap.utility.bundles.Formats; import com.mugames.vidsnap.ui.fragments.QualityFragment; public interface UtilityInterface { - interface DownloadUICallBack { - void onDownloadMP4ButtonPressed(String fileName); - void onDownloadMP3ButtonPressed(String fileName); - void onShareButtonPressed(String fileName); - - void onSelectedItem(int position, QualityFragment qualityFragment); + interface DownloadClickedCallback { + void onDownloadButtonPressed(); } interface JSInterface { - Object resf(Object[] values); - } - interface MiniExecutorCallBack { - void onBitmapReceive(Bitmap image); - - void onSizeReceived(long size, int position); - } interface ResponseCallBack { void onReceive(Response response); @@ -64,16 +54,14 @@ interface LoginIdentifier { } interface DownloadCallback { - void onDownloadCompleted(int id); + void onDownloadCompleted(DownloadDetails downloadDetails); + void onDownloadFailed(String reason, Exception e); } interface AnalyzeCallback { void onAnalyzeCompleted(Formats formats); } - interface AnalyzeUICallback { - void onAnalyzeCompleted(boolean isMultipleFile); - } interface TouchCallback { @@ -101,7 +89,7 @@ interface DialogueInterface { } interface LoginHelper{ - void signInNeeded(String reason, String loginURL, String[] loginDoneUrl, int cookiesKey, UtilityInterface.LoginIdentifier identifier); + void signInNeeded(UtilityClass.LoginDetailsProvider loginDetailsProvider); String getCookies(int cookiesKey); } } diff --git a/app/src/main/java/com/mugames/vidsnap/utility/VideoSharedBroadcast.java b/app/src/main/java/com/mugames/vidsnap/utility/VideoSharedBroadcast.java new file mode 100644 index 0000000..db63890 --- /dev/null +++ b/app/src/main/java/com/mugames/vidsnap/utility/VideoSharedBroadcast.java @@ -0,0 +1,60 @@ +/* + * This file is part of VidSnap. + * + * VidSnap is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * VidSnap is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with VidSnap. If not, see . + * + */ + +package com.mugames.vidsnap.utility; + +import static com.mugames.vidsnap.utility.Statics.OUTFILE_URI; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; + +import androidx.lifecycle.ViewModelProvider; + +import com.mugames.vidsnap.storage.FileUtil; +import com.mugames.vidsnap.ui.viewmodels.MainActivityViewModel; + +import java.io.File; +import java.util.Timer; +import java.util.TimerTask; + +public class VideoSharedBroadcast extends BroadcastReceiver { + + public static final String RESULT_BUNDLE = "com.mugames.vidsnap.utitlty.VideoSharedBroadcast.RESULT_BUNDLE"; + + @Override + public void onReceive(Context context, Intent intent) { + long waitTime = 30 * 1000; + Log.d("MUTube", "onReceive: VideoSharedBroadcast trigreed and media will be deleted with in 30 sec"); + Bundle tempResultBundle = intent.getBundleExtra(RESULT_BUNDLE); + Uri uri = Uri.parse(tempResultBundle.getString(OUTFILE_URI)); + new Timer().schedule(new TimerTask() { + @Override + public void run() { + FileUtil.deleteFile( + context.getExternalFilesDir(null) + File.separator + uri.getLastPathSegment(), + null + ); + Log.d("MUTube", "run: file deleted" + uri); + } + }, waitTime); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mugames/vidsnap/utility/bundles/DownloadDetails.java b/app/src/main/java/com/mugames/vidsnap/utility/bundles/DownloadDetails.java index 6e5beef..6e6fbcf 100644 --- a/app/src/main/java/com/mugames/vidsnap/utility/bundles/DownloadDetails.java +++ b/app/src/main/java/com/mugames/vidsnap/utility/bundles/DownloadDetails.java @@ -181,6 +181,6 @@ public static DownloadDetails findDetails(int id) { downloadDetailsList) { if (details.id == id) return details; } - throw new IllegalArgumentException("id: "+id+" has no details"); + throw new IllegalArgumentException("id: "+id+" has no details\nAvailable are"+downloadDetailsList); } }