/*** Copyright (c) 2012-2013 Samuele Rini This program 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 (at your option) any later version. This program 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 this program. If not, see http://www.gnu.org/licenses *** https://github.com/dentex/ytdownloader/ https://sourceforge.net/projects/ytdownloader/ *** Different Licenses and Credits where noted in code comments. */ package dentex.youtube.downloader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import java.net.URLDecoder; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.NoSuchElementException; import java.util.regex.Matcher; import java.util.regex.Pattern; import android.app.Activity; import android.app.AlertDialog; import android.content.ActivityNotFoundException; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.AssetManager; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.text.TextUtils; import android.util.Log; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemLongClickListener; import android.widget.Button; import android.widget.CheckBox; import android.widget.ImageView; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import com.bugsense.trace.BugSenseHandler; import com.impressionapps.vdownload.R; import com.jeremyfeinstein.slidingmenu.lib.SlidingMenu; import com.matsuhiro.android.connect.NetworkUtils; import com.matsuhiro.android.download.DownloadTask; import com.matsuhiro.android.download.DownloadTaskListener; import com.matsuhiro.android.download.Maps; import com.startapp.android.publish.StartAppAd; import dentex.youtube.downloader.menu.AboutActivity; import dentex.youtube.downloader.menu.DonateActivity; import dentex.youtube.downloader.menu.TutorialsActivity; import dentex.youtube.downloader.queue.FFmpegExtractAudioTask; import dentex.youtube.downloader.utils.FetchUrl; import dentex.youtube.downloader.utils.Json; import dentex.youtube.downloader.utils.PopUps; import dentex.youtube.downloader.utils.RhinoRunner; import dentex.youtube.downloader.utils.Utils; public class ShareActivity extends Activity { private static final String _VO_WEBM_480P = "VO - WebM - 480p"; private static final String _VO_MP4_1080P_HBR = "VO - MP4 - 1080p (HBR)"; private static final String _VO_WEBM_1080P = "VO - WebM - 1080p"; private static final String _VO_WEBM_720P = "VO - WebM - 720p"; private static final String _VO_WEBM_360P = "VO - WebM - 360p"; private static final String _VO_WEBM_240P = "VO - WebM - 240p"; private static final String _AO_OGG_HI_Q = "AO - OGG - Hi-Q"; private static final String _AO_OGG_MED_Q = "AO - OGG - Med-Q"; private static final String _VO_MP4_144P = "VO - MP4 - 144p"; private static final String _AO_M4A_HI_Q = "AO - M4A - Hi-Q"; private static final String _AO_M4A_MED_Q = "AO - M4A - Med-Q"; private static final String _AO_M4A_LOW_Q = "AO - M4A - Low-Q"; private static final String _VO_MP4_ORIGINAL = "VO - MP4 - Original"; private static final String _VO_MP4_1080P = "VO - MP4 - 1080p"; private static final String _VO_MP4_720P = "VO - MP4 - 720p"; private static final String _VO_MP4_480P = "VO - MP4 - 480p"; private static final String _VO_MP4_360P = "VO - MP4 - 360p"; private static final String _VO_MP4_240P = "VO - MP4 - 240p"; private static final String _WEBM_720P_3D = "WebM - 720p (3D)"; private static final String _WEBM_360P_3D = "WebM - 360p (3D)"; private static final String _MP4_520P_3D = "MP4 - 520p (3D)"; private static final String _MP4_720P_3D = "MP4 - 720p (3D)"; private static final String _MP4_240P_3D = "MP4 - 240p (3D)"; private static final String _MP4_360P_3D = "MP4 - 360p (3D)"; private static final String _WEBM_1080P = "WebM - 1080p"; private static final String _WEBM_720P = "WebM - 720p"; private static final String _WEBM_480P = "WebM - 480p"; private static final String _WEBM_360P = "WebM - 360p"; private static final String _MP4_ORIGINAL = "MP4 - Original"; private static final String _MP4_1080P = "MP4 - 1080p"; private static final String _3GP_240P = "3GP - 240p"; private static final String _FLV_480P = "FLV - 480p"; private static final String _FLV_360P = "FLV - 360p"; private static final String _MP4_720P = "MP4 - 720p"; private static final String _MP4_270P_360P = "MP4 - 270p/360p"; private static final String _3GP_144P = "3GP - 144p"; private static final String _FLV_270P = "FLV - 270p"; private static final String _FLV_240P = "FLV - 240p"; private static final String _MP4_480P = "MP4 - 480p"; private static final String _MP4_360P = "MP4 - 360p"; private static final String _UNKNOWN = "Unknown"; private ProgressBar progressBar1; private ProgressBar progressBarD; private ProgressBar progressBarL; private static final String DEBUG_TAG = "ShareActivity"; private TextView tv; private TextView noVideoInfo; private ListView lv; private static ShareActivityAdapter aA; //private static ShareActivityAdapterSimple aA; //private static ArrayAdapter<String> aA; private static List<String> links = new ArrayList<String>(); private static List<String> codecs = new ArrayList<String>(); private static List<String> qualities = new ArrayList<String>(); private static List<String> sizes = new ArrayList<String>(); private static List<String> itagsText = new ArrayList<String>(); private static List<Integer> itags = new ArrayList<Integer>(); private static List<ShareActivityListItem> listEntries = new ArrayList<ShareActivityListItem>(); private String titleRaw; private String basename; private int pos; private File path; private String validatedLink; private String vFilename = ""; public static Uri videoUri; private int icon; private CheckBox showAgain1; private CheckBox showAgain2; private TextView userFilename; private boolean sshInfoCheckboxEnabled; private boolean generalInfoCheckboxEnabled; private boolean fileRenameEnabled; private File chooserFolder; private AsyncDownload asyncDownload; private AsyncSizesFiller asyncSizesFiller; private boolean isAsyncDownloadRunning = false; private boolean isAsyncSizesFillerRunning = false; private AlertDialog helpDialog; private AlertDialog.Builder helpBuilder; private Bitmap img; private ImageView imgView; private String videoId; public static Context sShare; private ContextThemeWrapper boxThemeContextWrapper = new ContextThemeWrapper(this, R.style.BoxTheme); private String[] decryptionArray = null; private String jslink; private String decryptionFunction; private DownloadTaskListener dtl; private boolean autoModeEnabled = false; private boolean restartModeEnabled = false; private String extraId; private boolean autoFFmpegTaskAlreadySent = false; private String mComposedName; //private String dashUrl = ""; //private String dashStartUrl; private SlidingMenu slMenu; private static CharSequence constraint; private StartAppAd startAppAd = new StartAppAd(this); public void onResume(){ super.onResume(); startAppAd.onResume(); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); BugSenseHandler.leaveBreadcrumb("ShareActivity_onCreate"); sShare = getBaseContext(); //Start of Start APP Code StartAppAd.init(this, "112656864", "212488832"); //End of Startapp Code // Theme init Utils.themeInit(this); setContentView(R.layout.activity_share); if (aA != null) aA.clear(); links.clear(); codecs.clear(); qualities.clear(); sizes.clear(); itagsText.clear(); itags.clear(); listEntries.clear(); String theme = YTD.settings.getString("choose_theme", "D"); int or = this.getResources().getConfiguration().orientation; boolean isLandscape = (or == 2) ? true : false; // configure the SlidingMenu slMenu = new SlidingMenu(this); slMenu.setMode(SlidingMenu.LEFT); slMenu.setShadowWidthRes(R.dimen.shadow_width); slMenu.setShadowDrawable(R.drawable.shadow); if (isLandscape) { slMenu.setBehindWidthRes(R.dimen.slidingmenu_width_landscape); slMenu.showMenu(); } else { slMenu.setBehindWidthRes(R.dimen.slidingmenu_width_portrait); slMenu.showContent(); } slMenu.setFadeDegree(0.35f); slMenu.setHapticFeedbackEnabled(true); slMenu.setTouchModeAbove(SlidingMenu.TOUCHMODE_FULLSCREEN); slMenu.attachToActivity(this, SlidingMenu.SLIDING_CONTENT); slMenu.setMenu(R.layout.menu_frame); getActionBar().setDisplayHomeAsUpEnabled(true); //showSizesInVideoList = YTD.settings.getBoolean("show_size_list", false); // Language init Utils.langInit(this); // loading views from the layout xml tv = (TextView) findViewById(R.id.textView1); noVideoInfo = (TextView) findViewById(R.id.share_activity_info); progressBarD = (ProgressBar) findViewById(R.id.progressBarD); progressBarL = (ProgressBar) findViewById(R.id.progressBarL); if (theme.equals("D")) { progressBar1 = progressBarD; progressBarL.setVisibility(View.GONE); } else { progressBar1 = progressBarL; progressBarD.setVisibility(View.GONE); } imgView = (ImageView)findViewById(R.id.imgview); lv = (ListView) findViewById(R.id.list); // YTD update initialization updateInit(); // Get intent, action and MIME type Intent intent = getIntent(); String action = intent.getAction(); String type = intent.getType(); if (Intent.ACTION_SEND.equals(action) && type != null) { BugSenseHandler.leaveBreadcrumb("Intent.ACTION_SEND"); if ("text/plain".equals(type)) { try { handleSendText(intent, action); Utils.logger("d", "handling ACTION_SEND", DEBUG_TAG); } catch (IOException e) { Log.e(DEBUG_TAG, "Error: " + e.getMessage(), e); } } } if (Intent.ACTION_VIEW.equals(action)) { BugSenseHandler.leaveBreadcrumb("Intent.ACTION_VIEW"); if (intent.hasCategory("AUTO")) { autoModeEnabled = true; extraId = intent.getStringExtra("id"); pos = intent.getIntExtra("position", 0); vFilename = intent.getStringExtra("filename"); mComposedName = Utils.getFileNameWithoutExt(vFilename); Utils.logger("i", "Auto Mode Enabled:" + "\n -> id: " + extraId + "\n -> position: " + pos + "\n -> filename: " + vFilename, DEBUG_TAG); } else if (intent.hasCategory("RESTART")) { restartModeEnabled = true; extraId = intent.getStringExtra("id"); Utils.logger("i", "Restart Mode Enabled:" + "\n -> id: " + extraId, DEBUG_TAG); } try { handleSendText(intent, action); Utils.logger("d", "handling ACTION_VIEW", DEBUG_TAG); } catch (IOException e) { Log.e(DEBUG_TAG, "Error: " + e.getMessage(), e); } } } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); // Checks the orientation of the screen if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { //Utils.logger("i", "...landscape", DEBUG_TAG); slMenu.setBehindWidthRes(R.dimen.slidingmenu_width_landscape); slMenu.showMenu(); } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){ //Utils.logger("i", "...portrait", DEBUG_TAG); slMenu.setBehindWidthRes(R.dimen.slidingmenu_width_portrait); slMenu.showContent(); } } public static Context getContext() { return sShare; } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.activity_share, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { super.onOptionsItemSelected(item); if (!autoModeEnabled) { switch(item.getItemId()){ case android.R.id.home: slMenu.toggle(); return true; case R.id.menu_donate: startActivity(new Intent(this, DonateActivity.class)); return true; case R.id.menu_settings: Intent sIntent = new Intent(this, SettingsActivity.class); sIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); startActivity(sIntent); return true; case R.id.menu_about: startActivity(new Intent(this, AboutActivity.class)); return true; case R.id.menu_dashboard: launchDashboardActivity(); return true; case R.id.menu_tutorials: startActivity(new Intent(this, TutorialsActivity.class)); return true; default: return super.onOptionsItemSelected(item); } } else { return super.onOptionsItemSelected(item); } } private void launchDashboardActivity() { Intent dashboardIntent = new Intent(this, DashboardActivity.class); dashboardIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); startActivity(dashboardIntent); } /*@Override protected void onStart() { super.onStart(); Utils.logger("v", "_onStart", DEBUG_TAG); } @Override protected void onRestart() { super.onRestart(); Utils.logger("v", "_onRestart"); } @Override public void onPause() { super.onPause(); Utils.logger("v", "_onPause"); } @Override protected void onStop() { super.onStop(); Utils.logger("v", "_onStop", DEBUG_TAG); }*/ @Override public void onBackPressed() { Utils.logger("v", "_onBackPressed", DEBUG_TAG); if (slMenu.isMenuShowing()) { slMenu.showContent(true); } else { Utils.logger("v", "_onBackPressed", DEBUG_TAG); startAppAd.onBackPressed(); startAppAd.showAd(); startAppAd.loadAd(); System.out.println("Back Button Pressed"); super.onBackPressed(); // To cancel the AsyncDownload AsyncSizesFiller tasks only on // back button pressed (not when switching to other activities) if (isAsyncDownloadRunning) { Utils.logger("v", "canceling asyncDownload", DEBUG_TAG); asyncDownload.cancel(true); } if (isAsyncSizesFillerRunning) { Utils.logger("v", "canceling asyncSizesFiller", DEBUG_TAG); asyncSizesFiller.cancel(true); } } } void handleSendText(Intent intent, String action) throws IOException { /*ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = connMgr.getActiveNetworkInfo(); if (networkInfo != null && networkInfo.isConnected()) {*/ if (NetworkUtils.isNetworkAvailable(sShare)) { String sharedText = null; if (action.equals(Intent.ACTION_SEND)) { sharedText = intent.getStringExtra(Intent.EXTRA_TEXT); } else if (action.equals(Intent.ACTION_VIEW)) { sharedText = intent.getDataString(); } if (sharedText != null) { if (linkValidator(sharedText) == "bad_link") { badOrNullLinkAlert(); } else if (sharedText != null) { showGeneralInfoTutorial(); asyncDownload = new AsyncDownload(); asyncDownload.execute(validatedLink); } } else { badOrNullLinkAlert(); } } else { progressBar1.setVisibility(View.GONE); tv.setVisibility(View.GONE); noVideoInfo.setText(getString(R.string.no_net)); noVideoInfo.setVisibility(View.VISIBLE); PopUps.showPopUp(getString(R.string.no_net), getString(R.string.no_net_dialog_msg), "alert", this); Button retry = (Button) findViewById(R.id.share_activity_retry_button); retry.setVisibility(View.VISIBLE); retry.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Utils.reload(ShareActivity.this); } }); } } public void badOrNullLinkAlert() { BugSenseHandler.leaveBreadcrumb("badOrNullLinkAlert"); progressBar1.setVisibility(View.GONE); PopUps.showPopUp(getString(R.string.error), getString(R.string.bad_link_dialog_msg), "alert", this); tv.setVisibility(View.GONE); noVideoInfo.setText(getString(R.string.bad_link)); noVideoInfo.setVisibility(View.VISIBLE); } private void showGeneralInfoTutorial() { generalInfoCheckboxEnabled = YTD.settings.getBoolean("general_info", true); if (generalInfoCheckboxEnabled == true) { AlertDialog.Builder adb = new AlertDialog.Builder(boxThemeContextWrapper); LayoutInflater adbInflater = LayoutInflater.from(ShareActivity.this); View generalInfo = adbInflater.inflate(R.layout.dialog_general_info, null); showAgain1 = (CheckBox) generalInfo.findViewById(R.id.showAgain1); showAgain1.setChecked(true); adb.setView(generalInfo); adb.setTitle(getString(R.string.tutorial_title)); adb.setPositiveButton("OK", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { if (!showAgain1.isChecked()) { YTD.settings.edit().putBoolean("general_info", false).commit(); sshInfoCheckboxEnabled = YTD.settings.getBoolean("general_info", true); Utils.logger("v", "generalInfoCheckboxEnabled: " + generalInfoCheckboxEnabled, DEBUG_TAG); } } }); if (! ((Activity) this).isFinishing()) { adb.show(); } } } private String linkValidator(String sharedText) { Pattern pattern = Pattern.compile("(http://|https://).*(v=.{11}).*"); Matcher matcher = pattern.matcher(sharedText); if (matcher.find()) { //validatedLink = matcher.group(1) + "www.youtube.com/watch?" + matcher.group(2); validatedLink = "http://www.youtube.com/watch?" + matcher.group(2); videoId = matcher.group(2).replace("v=", ""); return validatedLink; } return "bad_link"; } public void assignPath() { boolean Location = YTD.settings.getBoolean("swap_location", false); if (Location == false) { String location = YTD.settings.getString("standard_location", "Downloads"); Utils.logger("d", "location: " + location, DEBUG_TAG); if (location.equals("DCIM") == true) { path = YTD.dir_DCIM; } if (location.equals("Movies") == true) { path = YTD.dir_Movies; } if (location.equals("Downloads") == true) { path = YTD.dir_Downloads; } } else { String cs = YTD.settings.getString("CHOOSER_FOLDER", ""); chooserFolder = new File(cs); if (chooserFolder.exists()) { Utils.logger("d", "chooserFolder: " + chooserFolder, DEBUG_TAG); path = chooserFolder; } else { path = YTD.dir_Downloads; Utils.logger("w", "chooserFolder not found, falling back to Download path", DEBUG_TAG); } } if (!path.exists()) { if (new File(path.getAbsolutePath()).mkdirs()) { Utils.logger("w", "destination path not found, creating it now", DEBUG_TAG); } else { Log.e(DEBUG_TAG, "Something really bad happened with the download destination..."); } } Utils.logger("d", "path: " + path, DEBUG_TAG); } private class AsyncDownload extends AsyncTask<String, Integer, String> { protected void onPreExecute() { isAsyncDownloadRunning = true; tv.setText(R.string.loading); progressBar1.setIndeterminate(true); progressBar1.setVisibility(View.VISIBLE); } protected String doInBackground(String... urls) { try { Utils.logger("d", "doInBackground...", DEBUG_TAG); assignBitmapToVideoListThumbnail(generateThumbUrls()); FetchUrl fu = new FetchUrl(sShare); String content = fu.doFetch(urls[0]); if (!content.isEmpty()) { return urlBlockMatchAndDecode(content); } else { return "e"; } } catch (Exception e) { Log.e(DEBUG_TAG, "downloadUrl: " + e.getMessage()); BugSenseHandler.sendExceptionMessage(DEBUG_TAG + "-> downloadUrl: ", e.getMessage(), e); return "e"; } } public void doProgress(int value){ publishProgress(value); } protected void onProgressUpdate(Integer... values) { progressBar1.setProgress(values[0]); } @Override protected void onPostExecute(String result) { progressBar1.setVisibility(View.GONE); isAsyncDownloadRunning = false; if (YTD.settings.getBoolean("show_thumb", false) && !((result == null || result.equals("e")) || (result != null && result.equals("login_required")) || (result != null && result.equals("rtmpe")) ) ) { imgView.setImageBitmap(img); } if (result == null || result.equals("e") && !autoModeEnabled) { BugSenseHandler.leaveBreadcrumb("invalid_url"); noVideosMsgs("alert", getString(R.string.invalid_url)); } if (result != null && result.equals("login_required") && !autoModeEnabled) { BugSenseHandler.leaveBreadcrumb("login_required"); noVideosMsgs("info", getString(R.string.login_required)); } if (result != null && result.equals("rtmpe")) { BugSenseHandler.leaveBreadcrumb("encrypted_streams"); listEntries.clear(); noVideosMsgs("info", getString(R.string.encrypted_streams)); } aA = new ShareActivityAdapter(listEntries, ShareActivity.this); //aA = new ArrayAdapter<String>(sShare, android.R.layout.simple_list_item_1, listEntries); if (autoModeEnabled) { BugSenseHandler.leaveBreadcrumb("autoModeEnabled"); assignPath(); try { callDownloadManager(links.get(pos), pos, vFilename, codecs.get(pos)); } catch (IndexOutOfBoundsException e) { Toast.makeText(ShareActivity.this, getString(R.string.video_list_error_toast), Toast.LENGTH_SHORT).show(); launchDashboardActivity(); } } else { ShareActivityListFilters.setupStoredFilters(ShareActivity.this, aA); //listEntriesBuilder(); lv.setAdapter(aA); if (!YTD.SHOW_ITAGS_AND_NO_SIZE_FOR_DUBUG) { asyncSizesFiller = new AsyncSizesFiller(); asyncSizesFiller.execute(links.toArray(new String[0])); } } tv.setText(titleRaw); lv.setOnItemClickListener(new OnItemClickListener() { public void onItemClick(AdapterView<?> parent, View view, int position, long id) { //Utils.logger("i", "Selected link: " + links.get(pos), DEBUG_TAG); BugSenseHandler.leaveBreadcrumb("ShareActivity_onItemClick"); assignPath(); // get the filtered position ShareActivityListItem item = aA.getItem(position); int currItag = item.getItag(); pos = itags.indexOf(currItag); Utils.logger("i", "click @ position: " + pos, DEBUG_TAG); //pos = 45; // to test IndexOutOfBound Exception... mComposedName = composeVideoFilenameNoExt(); vFilename = composeVideoFilename(mComposedName); helpBuilder = new AlertDialog.Builder(boxThemeContextWrapper); helpBuilder.setIcon(android.R.drawable.ic_dialog_info); helpBuilder.setTitle(getString(R.string.list_click_dialog_title)); boolean showSize = false; try { if (sizes.get(pos).equals("")) { helpBuilder.setMessage(titleRaw + "\n" + getString(R.string.quality) + " " + itagsText.get(pos)); } else { helpBuilder.setMessage(titleRaw + "\n" + getString(R.string.quality) + " " + itagsText.get(pos) + getString(R.string.size) + " " + sizes.get(pos).replace(" - ", "")); } } catch (IndexOutOfBoundsException e) { Toast.makeText(ShareActivity.this, getString(R.string.video_list_error_toast), Toast.LENGTH_SHORT).show(); } helpBuilder.setPositiveButton(getString(R.string.list_click_download_local), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { try { fileRenameEnabled = YTD.settings.getBoolean("enable_rename", false); if (fileRenameEnabled == true) { AlertDialog.Builder adb = new AlertDialog.Builder(boxThemeContextWrapper); LayoutInflater adbInflater = LayoutInflater.from(ShareActivity.this); View inputFilename = adbInflater.inflate(R.layout.dialog_input_filename, null); userFilename = (TextView) inputFilename.findViewById(R.id.input_filename); userFilename.setText(mComposedName); adb.setView(inputFilename); adb.setTitle(getString(R.string.rename_dialog_title)); adb.setMessage(getString(R.string.rename_dialog_msg)); adb.setPositiveButton("OK", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { mComposedName = userFilename.getText().toString(); vFilename = composeVideoFilename(mComposedName); callDownloadManager(links.get(pos), pos, vFilename, codecs.get(pos)); launchDashboardActivity(); } }); adb.setNegativeButton(getString(R.string.dialogs_negative), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { // cancel } }); if (! ((Activity) ShareActivity.this).isFinishing()) { adb.show(); } } else { callDownloadManager(links.get(pos), pos, vFilename, codecs.get(pos)); launchDashboardActivity(); } } catch (IndexOutOfBoundsException e) { Toast.makeText(ShareActivity.this, getString(R.string.video_list_error_toast), Toast.LENGTH_SHORT).show(); } } }); // show central button for SSH send if enabled in prefs // if (!YTD.settings.getBoolean("ssh_to_longpress_menu", false)) { // helpBuilder.setNeutralButton(getString(R.string.list_click_download_ssh), new DialogInterface.OnClickListener() { // // public void onClick(DialogInterface dialog, int which) { // sendViaSsh(); // } // }); // } helpBuilder.setNegativeButton(getString(R.string.dialogs_negative), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { //Toast.makeText(ShareActivity.this, "Download canceled...", Toast.LENGTH_SHORT).show(); } }); if (!showSize) { helpDialog = helpBuilder.create(); if (! ((Activity) ShareActivity.this).isFinishing()) { helpDialog.show(); } } } }); lv.setLongClickable(true); lv.setOnItemLongClickListener(new OnItemLongClickListener() { @Override public boolean onItemLongClick(AdapterView<?> parent, View view, final int position, long id) { BugSenseHandler.leaveBreadcrumb("ShareActivity_onItemLongClick"); pos = position; String base = composeVideoFilenameNoExt(); vFilename = composeVideoFilename(base); AlertDialog.Builder builder = new AlertDialog.Builder(boxThemeContextWrapper); if (!YTD.settings.getBoolean("ssh_to_longpress_menu", false)) { builder.setTitle(R.string.long_click_title).setItems(R.array.long_click_entries, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { switch (which) { case 0: // copy copy(position); break; case 1: // share share(position, vFilename); } } }); } else { builder.setTitle(R.string.long_click_title).setItems(R.array.long_click_entries2, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { switch (which) { case 0: // copy copy(position); break; case 1: // share share(position, vFilename); break; case 2: // SSH sendViaSsh(); } } }); } builder.create(); if (! ((Activity) ShareActivity.this).isFinishing()) { builder.show(); } return true; } }); } private void noVideosMsgs(String type, String cause) { PopUps.showPopUp(getString(R.string.no_video_available), cause, type, ShareActivity.this); tv.setVisibility(View.GONE); noVideoInfo.setVisibility(View.VISIBLE); } private void share(final int position, String filename) { BugSenseHandler.leaveBreadcrumb("ShareActivity_share"); Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND); sharingIntent.setType("text/plain"); sharingIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, filename); sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, links.get(position)); startActivity(Intent.createChooser(sharingIntent, getString(R.string.share_link_via))); } private void copy(final int position) { BugSenseHandler.leaveBreadcrumb("ShareActivity_copy"); ClipData cmd = ClipData.newPlainText("simple text", links.get(position)); ClipboardManager cb = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); cb.setPrimaryClip(cmd); Toast.makeText(ShareActivity.this, getString(R.string.link_copied), Toast.LENGTH_SHORT).show(); } private String composeVideoFilenameNoExt() { String suffix = itagsText.get(pos) .replace("MP4 - ", "") .replace("WebM - ", "") .replace("FLV - ", "") .replace("3GP - ", "") .replace("M4A - ", "") .replace("OGG - ", "") .replace("/", "-") .replace(" - ", "_"); String composedName = basename + "_" + suffix; Utils.logger("d", "videoFilename with no EXT: " + composedName, DEBUG_TAG); return composedName; } private String composeVideoFilename(String base) { String composedName = base + "." + codecs.get(pos); Utils.logger("d", "COMPLETE videoFilename: " + composedName, DEBUG_TAG); return composedName; } private void callConnectBot() { BugSenseHandler.leaveBreadcrumb("callConnectBot"); Context context = getApplicationContext(); PackageManager pm = context.getPackageManager(); final String connectBotFlavour = YTD.settings.getString("connectbot_flavour", "org.connectbot"); String connectBotFlavourPlain = "ConnectBot"; if (connectBotFlavour.equals("sk.vx.connectbot")) connectBotFlavourPlain = "VX " + connectBotFlavourPlain; if (connectBotFlavour.equals("org.woltage.irssiconnectbot")) connectBotFlavourPlain = "Irssi " + connectBotFlavourPlain; Intent appStartIntent = pm.getLaunchIntentForPackage(connectBotFlavour); if (null != appStartIntent) { Utils.logger("d", "appStartIntent: " + appStartIntent, DEBUG_TAG); context.startActivity(appStartIntent); } else { AlertDialog.Builder cb = new AlertDialog.Builder(boxThemeContextWrapper); cb.setTitle(getString(R.string.callConnectBot_dialog_title, connectBotFlavourPlain)); cb.setMessage(getString(R.string.callConnectBot_dialog_msg)); icon = android.R.drawable.ic_dialog_alert; cb.setIcon(icon); cb.setPositiveButton(getString(R.string.callConnectBot_dialog_positive), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData(Uri.parse("market://details?id=" + connectBotFlavour)); try { startActivity(intent); } catch (ActivityNotFoundException exception){ PopUps.showPopUp(getString(R.string.no_market), getString(R.string.no_net_dialog_msg), "alert", ShareActivity.this); } } }); cb.setNegativeButton(getString(R.string.dialogs_negative), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { // cancel } }); AlertDialog helpDialog = cb.create(); if (! ((Activity) ShareActivity.this).isFinishing()) { helpDialog.show(); } } } private void sendViaSsh() { BugSenseHandler.leaveBreadcrumb("sendViaSsh"); try { String wgetCmd; Boolean shortSshCmdEnabled = YTD.settings.getBoolean("enable_connectbot_short_cmd", false); if (shortSshCmdEnabled) { wgetCmd = "wget -e \"convert-links=off\" --keep-session-cookies --save-cookies /dev/null --no-check-certificate \'" + links.get(pos) + "\' -O " + vFilename; } else { wgetCmd = "REQ=`wget -q -e \"convert-links=off\" --keep-session-cookies --save-cookies /dev/null --no-check-certificate \'" + validatedLink + "\' -O-` && urlblock=`echo $REQ | grep -oE \'url_encoded_fmt_stream_map\": \".*\' | sed -e \'s/\", \".*//\'" + " -e \'s/url_encoded_fmt_stream_map\": \"//\'` && urlarray=( `echo $urlblock | sed \'s/,/\\n\\n/g\'` ) && N=" + pos + " && block=`echo \"${urlarray[$N]}\" | sed -e \'s/%3A/:/g\' -e \'s/%2F/\\//g\' -e \'s/%3F/\\?/g\' -e \'s/%3D/\\=/g\'" + " -e \'s/%252C/%2C/g\' -e \'s/%26/\\&/g\' -e \'s/%253A/\\:/g\' -e \'s/\", \"/\"-\"/\' -e \'s/sig=/signature=/\'" + " -e \'s/x-flv/flv/\' -e \'s/\\\\\\u0026/\\&/g\'` && url=`echo $block | grep -oE \'http://.*\' | sed -e \'s/&type=.*//\'" + " -e \'s/&signature=.*//\' -e \'s/&quality=.*//\' -e \'s/&fallback_host=.*//\'` && sig=`echo $block | " + "grep -oE \'signature=.{81}\'` && downloadurl=`echo $url\\&$sig | sed \'s/&itag=[0-9][0-9]&signature/\\&signature/\'` && " + "wget -e \"convert-links=off\" --keep-session-cookies --save-cookies /dev/null --tries=5 --timeout=45 --no-check-certificate " + "\"$downloadurl\" -O " + vFilename; } Utils.logger("d", "wgetCmd: " + wgetCmd, DEBUG_TAG); ClipData cmd = ClipData.newPlainText("simple text", wgetCmd); ClipboardManager cb = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); cb.setPrimaryClip(cmd); sshInfoCheckboxEnabled = YTD.settings.getBoolean("ssh_info", true); if (sshInfoCheckboxEnabled == true) { AlertDialog.Builder adb = new AlertDialog.Builder(boxThemeContextWrapper); LayoutInflater adbInflater = LayoutInflater.from(ShareActivity.this); View showAgain = adbInflater.inflate(R.layout.dialog_inflatable_checkbox, null); showAgain2 = (CheckBox) showAgain.findViewById(R.id.infl_cb); showAgain2.setChecked(true); showAgain2.setText(getString(R.string.show_again_checkbox)); adb.setView(showAgain); adb.setTitle(getString(R.string.ssh_info_tutorial_title)); adb.setMessage(getString(R.string.ssh_info_tutorial_msg)); adb.setPositiveButton("OK", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { if (!showAgain2.isChecked()) { YTD.settings.edit().putBoolean("ssh_info", false).apply(); Utils.logger("d", "sshInfoCheckboxEnabled: " + false, DEBUG_TAG); } callConnectBot(); } }); if (! ((Activity) ShareActivity.this).isFinishing()) { adb.show(); } } else { callConnectBot(); } } catch (IndexOutOfBoundsException e) { Toast.makeText(ShareActivity.this, getString(R.string.video_list_error_toast), Toast.LENGTH_SHORT).show(); } } } private void callDownloadManager(final String link, final int position, final String nameOfVideo, final String vExt) { BugSenseHandler.leaveBreadcrumb("callDownloadManager"); dtl = new DownloadTaskListener() { @Override public void preDownload(DownloadTask task) { long ID = task.getDownloadId(); String pathOfVideo = task.getPath(); String jsonDataType = task.getType(); String aExt = task.getAudioExt(); Utils.logger("d", "__preDownload on ID: " + ID, DEBUG_TAG); Maps.mNetworkSpeedMap.put(ID, (long) 0); Json.addEntryToJsonFile( sShare, String.valueOf(ID), jsonDataType, videoId, pos, YTD.JSON_DATA_STATUS_IN_PROGRESS, pathOfVideo, nameOfVideo, mComposedName, aExt, "-", false); writeThumbToDisk(); if (!autoModeEnabled) YTD.sequence.add(ID); YTD.NotificationHelper(sShare); } @Override public void updateProcess(DownloadTask task) { // nothing to do } @Override public void finishDownload(DownloadTask task) { long ID = task.getDownloadId(); String nameOfVideo = task.getFileName(); String pathOfVideo = task.getPath(); String jsonDataType = task.getType(); String aExt = task.getAudioExt(); Utils.logger("d", "__finishDownload on ID: " + ID, DEBUG_TAG); Utils.scanMedia(getApplicationContext(), new String[] { path.getPath() + File.separator + nameOfVideo }, new String[] {"video/*"}); String size; try { long downloadTotalSize = task.getTotalSize(); //Maps.mTotalSizeMap.get(ID); size = String.valueOf(Utils.MakeSizeHumanReadable(downloadTotalSize, false)); } catch (NullPointerException e) { Utils.logger("w", "NPE getting finished download size for ID: " + ID, DEBUG_TAG); size = "-"; } Json.addEntryToJsonFile( sShare, String.valueOf(ID), jsonDataType, videoId, pos, YTD.JSON_DATA_STATUS_COMPLETED, pathOfVideo, nameOfVideo, mComposedName, aExt, size, false); if (DashboardActivity.isDashboardRunning) DashboardActivity.refreshlist(DashboardActivity.sDashboardActivity); YTD.removeIdUpdateNotification(ID); YTD.videoinfo.edit().remove(String.valueOf(ID) + "_link").commit(); //YTD.videoinfo.edit().remove(String.valueOf(ID) + "_position").commit(); Maps.removeFromAllMaps(ID); if (YTD.settings.getBoolean("ffmpeg_auto_cb", false) && !autoFFmpegTaskAlreadySent) { Utils.logger("d", "autoFfmpeg enabled: enqueing task for id: " + ID, DEBUG_TAG); autoFFmpegTaskAlreadySent = true; String[] bitrateData = null; String brType = null; String brValue = null; String audioFileName; String extrType = YTD.settings.getString("audio_extraction_type", "conv"); if (extrType.equals("conv")) { bitrateData = Utils.retrieveBitrateValuesFromPref(sShare); audioFileName = mComposedName + "_" + bitrateData[0] + "-" + bitrateData[1] + ".mp3"; brType = bitrateData[0]; brValue = bitrateData[1]; } else { audioFileName = mComposedName + aExt; } File audioFile = new File(path.getPath(), audioFileName); if (!audioFile.exists()) { File videoFileToConvert = new File(path.getPath(), vFilename); YTD.queueThread.enqueueTask(new FFmpegExtractAudioTask( sShare, videoFileToConvert, audioFile, brType, brValue, String.valueOf(ID), videoId, pos), 0); } } else { Utils.logger("v", "Auto FFmpeg task for ID " + ID + " not enabled OR already sent for this video", DEBUG_TAG); } } @Override public void errorDownload(DownloadTask task, Throwable error) { long ID = task.getDownloadId(); String nameOfVideo = task.getFileName(); String pathOfVideo = task.getPath(); String jsonDataType = task.getType(); String aExt = task.getAudioExt(); Utils.logger("w", "__errorDownload on ID: " + ID, DEBUG_TAG); Toast.makeText(sShare, nameOfVideo + ": " + getString(R.string.download_failed), Toast.LENGTH_SHORT).show(); String status = YTD.JSON_DATA_STATUS_PAUSED; String size = "-"; if (error != null && error.getMessage() != null) { Pattern httpPattern = Pattern.compile("http error code: (400|403|404|405|410|411)"); Matcher httpMatcher = httpPattern.matcher(error.getMessage()); if (httpMatcher.find()) { status = YTD.JSON_DATA_STATUS_FAILED; Utils.logger("w", httpMatcher.group(1) + " Client Error for ID: " + ID, DEBUG_TAG); } } try { Long bytes_downloaded = Maps.mDownloadSizeMap.get(ID); Long bytes_total = Maps.mTotalSizeMap.get(ID); String progress = String.valueOf(Maps.mDownloadPercentMap.get(ID)); String readableBytesDownloaded = Utils.MakeSizeHumanReadable(bytes_downloaded, false); String readableBytesTotal = Utils.MakeSizeHumanReadable(bytes_total, false); String progressRatio = readableBytesDownloaded + "/" + readableBytesTotal; size = progressRatio + " (" + progress + "%)"; } catch (NullPointerException e) { Utils.logger("w", "errorDownload: NPE @ DM Maps", DEBUG_TAG); } Json.addEntryToJsonFile( sShare, String.valueOf(ID), jsonDataType, videoId, pos, status, pathOfVideo, nameOfVideo, mComposedName, aExt, size, false); if (DashboardActivity.isDashboardRunning) DashboardActivity.refreshlist(DashboardActivity.sDashboardActivity); YTD.removeIdUpdateNotification(ID); } }; File dest = new File(path, vFilename); File destTemp = new File(path, vFilename + DownloadTask.TEMP_SUFFIX); String previousJson = Json.readJsonDashboardFile(sShare); String aExt = findAudioCodec(); String jsonDataType; if (vExt.equals("m4a") || codecs.get(pos).equals("ogg")) { jsonDataType = YTD.JSON_DATA_TYPE_A_E; } else { jsonDataType = YTD.JSON_DATA_TYPE_V; } boolean blockDashboardLaunch = false; if (dest.exists() || (destTemp.exists() && previousJson.contains(dest.getName())) && !autoModeEnabled && !restartModeEnabled) { blockDashboardLaunch = true; PopUps.showPopUp(getString(R.string.long_press_warning_title), getString(R.string.menu_import_double), "info", ShareActivity.this); } else { long id = 0; if (autoModeEnabled || restartModeEnabled) { id = Long.parseLong(extraId); } else { id = System.currentTimeMillis(); } try { DownloadTask dt = new DownloadTask(this, id, link, vFilename, path.getPath(), aExt, jsonDataType, dtl, false); YTD.videoinfo.edit().putString(String.valueOf(id) + "_link", link).apply(); //YTD.videoinfo.edit().putInt(String.valueOf(id) + "_position", position).apply(); Maps.dtMap.put(id, dt); dt.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } catch (MalformedURLException e) { Log.e(DEBUG_TAG, "unable to start Download Manager -> " + e.getMessage()); } } if (autoModeEnabled && !blockDashboardLaunch) { launchDashboardActivity(); } } private String findAudioCodec() { String aExt = null; if (codecs.get(pos).equals("webm")) aExt = ".ogg"; if (codecs.get(pos).equals("mp4")) aExt = ".aac"; if (codecs.get(pos).equals("flv") && qualities.get(pos).equals("small")) aExt = ".mp3"; if (codecs.get(pos).equals("flv") && qualities.get(pos).equals("medium")) aExt = ".aac"; if (codecs.get(pos).equals("flv") && qualities.get(pos).equals("large")) aExt = ".aac"; if (codecs.get(pos).equals("3gp")) aExt = ".aac"; if (codecs.get(pos).equals("m4a")) aExt = ".aac"; if (codecs.get(pos).equals("ogg")) aExt = ".ogg"; return aExt; } private String findMatchGroupOne(String text, String regEx) { Pattern pattern = Pattern.compile(regEx); Matcher matcher = pattern.matcher(text); if (matcher.find()) return matcher.group(1); return ""; } private String urlBlockMatchAndDecode(String content) { // log entire YouTube requests //File req = new File(YTD.dir_Downloads, "YTD_yt_req.txt"); //Utils.appendStringToFile(req, content); if (content.equals("e")) return "e"; if (asyncDownload.isCancelled()) { Utils.logger("d", "asyncDownload cancelled @ urlBlockMatchAndDecode begin", DEBUG_TAG); return "Cancelled!"; } Pattern rtmpePattern = Pattern.compile("rtmpe=yes|conn=rtmpe"); Matcher rtmpeMatcher = rtmpePattern.matcher(content); if (rtmpeMatcher.find()) { return "rtmpe"; } Pattern loginPattern = Pattern.compile("restrictions:age"); Matcher loginMatcher = loginPattern.matcher(content); if (loginMatcher.find()) { return "login_required"; } findVideoFilenameBase(content); findJs(content); String ueStreams = findMatchGroupOne(content, "url_encoded_fmt_stream_map\\\": \\\"(.*?)\\\""); String asStreams = findMatchGroupOne(content, "adaptive_fmts\\\": \\\"(.*?)\\\""); findItags(ueStreams + "," + asStreams); boolean asEnabled = YTD.settings.getBoolean("enable_adaptive", false); if (asEnabled || autoModeEnabled) { return splitStreamsGroups(ueStreams + "," + asStreams, content); } else { return splitStreamsGroups(ueStreams, content); } } private void findItags(String streams) { Pattern blockPattern = Pattern.compile(","); Matcher blockMatcher = blockPattern.matcher(streams); if (blockMatcher.find() && !asyncDownload.isCancelled()) { String[] blocks = streams.split(blockPattern.toString()); int count = blocks.length-1; int i = 0; while (i < count && !asyncDownload.isCancelled()) { itagMatcher(blocks[i], i, false); i++; } int[] log = new int[itags.size()]; for (int j = 0; j < itags.size(); j++) log[j] = itags.get(j); Utils.logger("v", "itags matched: " + Arrays.toString(log), DEBUG_TAG); } } private String splitStreamsGroups(String streams, String content) { Pattern blockPattern = Pattern.compile(","); Matcher blockMatcher = blockPattern.matcher(streams); if (blockMatcher.find() && !asyncDownload.isCancelled()) { String[] blocks = streams.split(blockPattern.toString()); int count = blocks.length-1; if (count == 0) return "e"; progressBar1.setIndeterminate(false); decryptionArray = null; int i = 0; Utils.logger("d", "*** decoded streams ***", DEBUG_TAG); while (i < count && !asyncDownload.isCancelled()) { try { blocks[i] = URLDecoder.decode(blocks[i], "UTF-8"); } catch (UnsupportedEncodingException e) { Log.e(DEBUG_TAG, "UnsupportedEncodingException @ splitStreamsGroups: " + e.getMessage()); } asyncDownload.doProgress((int) ((i / count) * 100)); Utils.logger("v", "index " + i + ", block: " + blocks[i], DEBUG_TAG); codecMatcher(blocks[i], i); qualityMatcher(blocks[i], i); itagMatcher(blocks[i], i, true); linkComposer(blocks[i], i); i++; } //findDashUrl(content); } else { Utils.logger("d", "asyncDownload cancelled @ 'matchUrlEncodedStreams' match", DEBUG_TAG); } return "ok"; } private class AsyncSizesFiller extends AsyncTask<String, String, Void> { protected void onPreExecute() { isAsyncSizesFillerRunning = true; Utils.logger("d", "*** sizes ***", DEBUG_TAG); } @Override protected Void doInBackground(String... urls) { for (int i = 0; i < urls.length; i++) { if (!this.isCancelled()) { String size = getVideoFileSize(urls[i]); if (size.equals("-")) { Utils.logger("w", "trying getVideoFileSize 2nd time", DEBUG_TAG); size = getVideoFileSize(urls[i]); } //Utils.logger("d", "index: " + i + ", size: " + size, DEBUG_TAG); Utils.logger("d", "size: " + size, DEBUG_TAG); publishProgress(String.valueOf(i), size); } } return null; } protected void onProgressUpdate(String... i) { Integer index = Integer.valueOf(i[0]); String newValue = i[1]; sizes.remove(index); sizes.add(index, " - " + newValue); listEntries.clear(); listEntriesBuilder(); aA.notifyDataSetChanged(); } @Override protected void onPostExecute(Void unused) { Utils.logger("v", "AsyncSizesFiller # onPostExecute", DEBUG_TAG); isAsyncSizesFillerRunning = false; } } private void findVideoFilenameBase(String content) { String title = findMatchGroupOne(content, "<title>(.*?)</title>"); if (!title.isEmpty()) { titleRaw = title.replaceAll("\\s*-\\s*YouTube", ""); titleRaw = android.text.Html.fromHtml(titleRaw).toString(); basename = titleRaw.replaceAll("\\W", "_"); } else { basename = "Youtube_Video"; } Utils.logger("d", "findVideoFilenameBase: " + basename, DEBUG_TAG); } public static void assignConstraint(CharSequence pConstraint) { constraint = pConstraint; listEntries.clear(); listEntriesBuilder(); aA.notifyDataSetChanged(); } public static void listEntriesBuilder() { for (int i = 0; i < itagsText.size(); i++) { if (constraint == null || TextUtils.isEmpty(constraint)) { try { listEntries.add(new ShareActivityListItem(itagsText.get(i) + sizes.get(i), itags.get(i))); } catch (NoSuchElementException e) { listEntries.add(new ShareActivityListItem("//", -1)); } catch (IndexOutOfBoundsException e) { listEntries.add(new ShareActivityListItem("--", -1)); } } else { String[] constraintItags = Pattern.compile("/", Pattern.LITERAL).split(constraint); for (int j = 0; j < constraintItags.length; j++) { if (itags.get(i) == Integer.parseInt(constraintItags[j])) { Utils.logger("i", "matched itag -> " + constraintItags[j], DEBUG_TAG); try { listEntries.add(new ShareActivityListItem(itagsText.get(i) + sizes.get(i), itags.get(i))); } catch (NoSuchElementException e) { listEntries.add(new ShareActivityListItem("//", -1)); } catch (IndexOutOfBoundsException e) { listEntries.add(new ShareActivityListItem("--", -1)); } } } } } } private void linkComposer(String block, int i) { Pattern urlPattern = Pattern.compile("url=(.+?)\\\\u0026"); Matcher urlMatcher = urlPattern.matcher(block); String url = null; if (urlMatcher.find()) { url = urlMatcher.group(1); } else { Pattern urlPattern2 = Pattern.compile("url=(.+?)$"); Matcher urlMatcher2 = urlPattern2.matcher(block); if (urlMatcher2.find()) { url = urlMatcher2.group(1); } else { Log.e(DEBUG_TAG, "index: " + i + "url: " + url); } } String sig = null; Pattern sigPattern = Pattern.compile("sig=(.+?)\\\\u0026"); Matcher sigMatcher = sigPattern.matcher(block); if (sigMatcher.find()) { sig = "signature=" + sigMatcher.group(1); Utils.logger("d", "index: " + i + ", non-ecrypted signature found on step 1", DEBUG_TAG); } else { Pattern sigPattern2 = Pattern.compile("sig=(.+?)$"); Matcher sigMatcher2 = sigPattern2.matcher(block); if (sigMatcher2.find()) { sig = "signature=" + sigMatcher2.group(1); Utils.logger("d", "index: " + i + ", non-ecrypted signature found on step 2", DEBUG_TAG); } else { Pattern sigPattern3 = Pattern.compile("sig=([[0-9][A-Z]]{39,40}\\.[[0-9][A-Z]]{39,40})"); Matcher sigMatcher3 = sigPattern3.matcher(block); if (sigMatcher3.find()) { sig = "signature=" + sigMatcher3.group(1); Utils.logger("d", "index: " + i + ", non-ecrypted signature found on step 3", DEBUG_TAG); } else { Pattern sigPattern4 = Pattern.compile("^s=(.+?)\\\\u0026"); Matcher sigMatcher4 = sigPattern4.matcher(block); if (sigMatcher4.find()) { Utils.logger("d", "index: " + i + ", encrypted signature found on step 1; length is " + sigMatcher4.group(1).length(), DEBUG_TAG); sig = "signature=" + decryptExpSig(sigMatcher4.group(1)); } else { Pattern sigPattern5 = Pattern.compile("\\\\u0026s=(.+?)\\\\u0026"); Matcher sigMatcher5 = sigPattern5.matcher(block); if (sigMatcher5.find()) { Utils.logger("d", "index: " + i + ", encrypted signature found on step 2; length is " + sigMatcher5.group(1).length(), DEBUG_TAG); sig = "signature=" + decryptExpSig(sigMatcher5.group(1)); } else { Pattern sigPattern6 = Pattern.compile("\\\\u0026s=(.+?)$"); Matcher sigMatcher6 = sigPattern6.matcher(block); if (sigMatcher6.find()) { Utils.logger("d", "index: " + i + ", encrypted signature found on step 3; length is " + sigMatcher6.group(1).length(), DEBUG_TAG); sig = "signature=" + decryptExpSig(sigMatcher6.group(1)); } else { Utils.logger("w", "index: " + i + ", sig: " + sig, DEBUG_TAG); } } } } } } Utils.logger("v", "index " + i + ", url: " + url, DEBUG_TAG); Utils.logger("v", "index " + i + ", sig: " + sig, DEBUG_TAG); String composedLink = url + "&" + sig; links.add(composedLink); //Utils.logger("i", composedLink); sizes.add(""); } private String decryptExpSig(String sig) { FetchUrl fu = new FetchUrl(sShare); if (decryptionArray == null) { String jsCode = null; if (!jslink.equals("e")) jsCode = fu.doFetch(jslink); String findSignatureCode = "function isInteger(n) {" + " return (typeof n==='number' && n%1==0);" + "}" + "function findSignatureCode(sourceCode) {" + " var functionNameMatches=sourceCode.match(/\\.signature\\s*=\\s*(\\w+)\\(\\w+\\)/);" + " var functionName=(functionNameMatches)?functionNameMatches[1]:null;" + " " + " var regCode=new RegExp('function '+functionName+" + " '\\\\s*\\\\(\\\\w+\\\\)\\\\s*{\\\\w+=\\\\w+\\\\.split\\\\(\"\"\\\\);(.+);return \\\\w+\\\\.join');" + " var functionCodeMatches=sourceCode.match(regCode);" + " var functionCode=(functionCodeMatches)?functionCodeMatches[1]:null;" + " " + " var regSlice=new RegExp('slice\\\\s*\\\\(\\\\s*(.+)\\\\s*\\\\)');" + " var regSwap=new RegExp('\\\\w+\\\\s*\\\\(\\\\s*\\\\w+\\\\s*,\\\\s*([0-9]+)\\\\s*\\\\)');" + " var regInline=new RegExp('\\\\w+\\\\[0\\\\]\\\\s*=\\\\s*\\\\w+\\\\[([0-9]+)\\\\s*%\\\\s*\\\\w+\\\\.length\\\\]');" + " var functionCodePieces = functionCode.split(';');" + " var decodeArray=[], signatureLength=81;" + " for (var i=0; i<functionCodePieces.length; i++) {" + " functionCodePieces[i]=functionCodePieces[i].trim();" + " if (functionCodePieces[i].length==0) {" + " } else if (functionCodePieces[i].indexOf('slice') >= 0) {" + " var sliceMatches=functionCodePieces[i].match(regSlice);" + " var slice=(sliceMatches)?sliceMatches[1]:null;" + " slice=parseInt(slice, 10);" + " if (isInteger(slice)){ " + " decodeArray.push(-slice);" + " signatureLength+=slice;" + " } " + " } else if (functionCodePieces[i].indexOf('reverse') >= 0) {" + " decodeArray.push(0);" + " } else if (functionCodePieces[i].indexOf('[0]') >= 0) {" + " if (i+2<functionCodePieces.length && " + " functionCodePieces[i+1].indexOf('.length') >= 0 &&" + " functionCodePieces[i+1].indexOf('[0]') >= 0) {" + " var inlineMatches=functionCodePieces[i+1].match(regInline);" + " var inline=(inlineMatches)?inlineMatches[1]:null;" + " inline=parseInt(inline, 10);" + " decodeArray.push(inline);" + " i+=2;" + " } " + " } else if (functionCodePieces[i].indexOf(',') >= 0) {" + " var swapMatches=functionCodePieces[i].match(regSwap);" + " var swap=(swapMatches)?swapMatches[1]:null;" + " swap=parseInt(swap, 10);" + " if (isInteger(swap)){" + " decodeArray.push(swap);" + " } " + " }" + " }" + " return decodeArray;" + "}"; decryptionArray = RhinoRunner.obtainDecryptionArray(jsCode, findSignatureCode); if (decryptionArray[0].equals("e")) decryptionArray = downloadHardCodedArray(); decryptionFunction = "function decryptSignature(a){ a=a.split(\"\"); "; for (int i = 0; i < decryptionArray.length; i++) { int rule = Integer.parseInt(decryptionArray[i]); if (rule == 0) decryptionFunction = decryptionFunction + "a=a.reverse(); "; if (rule < 0) decryptionFunction = decryptionFunction + "a=a.slice("+ -rule +"); "; if (rule > 0) decryptionFunction = decryptionFunction + "a=swap(a,"+ rule +"); "; } decryptionFunction = decryptionFunction + "return a.join(\"\")} function swap(a,b){ var c=a[0]; a[0]=a[b%a.length]; a[b]=c; return a };"; Utils.logger("i", "decryptionArray: " + Arrays.toString(decryptionArray), DEBUG_TAG); Utils.logger("i", "decryptionFunction: " + decryptionFunction, DEBUG_TAG); } String signature = RhinoRunner.decipher(sig, decryptionFunction); return signature; } private String[] downloadHardCodedArray() { Utils.logger("w", "downloading hard-coded decryption array", DEBUG_TAG); FetchUrl fu = new FetchUrl(sShare); String arrayLink = "http://sourceforge.net/projects/ytdownloader/files/utils/array/download"; String as = fu.doFetch(arrayLink); String[] arr = Pattern.compile(",", Pattern.LITERAL).split(as.replaceAll("\\n", "")); return arr; } private void findJs(String content) { String jslinkRaw = findMatchGroupOne(content, "\"js\":\\s*\"([^\"]+)\""); if (!jslinkRaw.isEmpty()) { if (!(jslinkRaw.indexOf("//") == 0)) { Utils.logger("w", "adding 'http:' to jslinkRaw", DEBUG_TAG); jslinkRaw = "http:" + jslinkRaw; } jslink = jslinkRaw.replaceAll("\\\\", ""); } else { jslink = "e"; } Utils.logger("v", "jslink: " + jslink, DEBUG_TAG); } /*@SuppressLint("DefaultLocale") private void findDashUrl(String content) { Utils.logger("d", "*** dash signated streams ***", DEBUG_TAG); String[] dashElements; String dashManifest = findMatchGroupOne(content, "\"dashmpd\":\\s*\"([^\"]+)\""); //Utils.logger("i", "dashManifest: " + dashManifest, DEBUG_TAG); if (!dashManifest.isEmpty()) { String dashParams = findMatchGroupOne(dashManifest, "youtube.com\\\\\\/api\\\\\\/manifest\\\\\\/dash\\\\\\/(.+)"); //Utils.logger("i", "dashParams: " + dashParams, DEBUG_TAG); if (!dashParams.isEmpty()) { dashElements = dashParams.split("\\\\\\/"); for (int i=0; i < dashElements.length; i+=2) { if (i>0) dashUrl = dashUrl + "&"; if (dashElements[i].equals("s")) { Utils.logger("i", "ecrypted signature found into dash manifest", DEBUG_TAG); dashUrl = dashUrl + ("signature=" + decryptExpSig(dashElements[i+1])); } else if (dashElements[i].equals("sig")) { dashUrl = dashUrl + ("signature=" + dashElements[i+1]); } else { dashUrl = dashUrl + (dashElements[i] + '=' + dashElements[i+1]); } } //Utils.logger("i", "dashUrl (partial): " + dashUrl, DEBUG_TAG); if (!links.get(0).isEmpty()) { dashStartUrl = findMatchGroupOne(links.get(0), "(http.*?videoplayback\\?)"); //Utils.logger("i", "dashStartURL: " + dashStartUrl, DEBUG_TAG); } if (!dashStartUrl.isEmpty()) { dashUrl = dashStartUrl + dashUrl; } else { dashUrl = ""; } if (!dashUrl.isEmpty()) { if (dashUrl.toLowerCase().indexOf("ratebypass") == -1) { dashUrl = dashUrl + "&ratebypass=yes"; } if (itags.contains(135)) addDashUrlEntries(3, dashUrl, "flv", "large", "35"); if (itags.contains(137) || itags.contains(264)) addDashUrlEntries(0, dashUrl, "mp4", "hd1080", "37"); if (itags.contains(138)) addDashUrlEntries(0, dashUrl, "mp4", "highres", "38"); if (itags.contains(248)) addDashUrlEntries(2, dashUrl, "webm", "hd1080", "46"); } } } } private void addDashUrlEntries(int i, String link, String codec, String quality, String itag) { links.add(i, link + "&itag=" + itag); codecs.add(i, codec); qualities.add(i, quality); sizes.add(i, ""); String itagText = findItag(itag); if (YTD.SHOW_ITAGS_AND_NO_SIZE_FOR_DUBUG) { itagsText.add(i, "[" + itag + "d]_" + itagText); } else { itagsText.add(i, itagText); } itags.add(i, Integer.parseInt(itag)); Utils.logger("d", "inserted at index: " + i + ", codec: " + codec, DEBUG_TAG); Utils.logger("d", "inserted at index: " + i + ", quality: " + quality, DEBUG_TAG); Utils.logger("d", "inserted at index: " + i + ", itag: " + itag + " (" + itagText + ")", DEBUG_TAG); Utils.logger("v", "inserted at index: " + i + ", url: " + link + "&itag=" + itag, DEBUG_TAG); }*/ private String getVideoFileSize(String link) { try { final URL url = new URL(link); URLConnection ucon = url.openConnection(); ucon.connect(); int file_size = ucon.getContentLength(); return Utils.MakeSizeHumanReadable(file_size, false); } catch(IOException e) { return "-"; } } private void codecMatcher(String current, int i) { Pattern codecPattern = Pattern.compile("(webm|mp4|flv|3gp)"); Matcher codecMatcher = codecPattern.matcher(current); if (codecMatcher.find()) { codecs.add(codecMatcher.group()); } else { codecs.add("video"); } Utils.logger("d", "index: " + i + ", Codec: " + codecs.get(i), DEBUG_TAG); } private void qualityMatcher(String current, int i) { Pattern qualityPattern = Pattern.compile("(highres|hd1080|hd720|large|medium|small)"); Matcher qualityMatcher = qualityPattern.matcher(current); if (qualityMatcher.find()) { qualities.add(qualityMatcher.group().replace("highres", "Original")); } else { qualities.add("-"); } Utils.logger("d", "index: " + i + ", Quality: " + qualities.get(i), DEBUG_TAG); } private void itagMatcher(String current, int i, boolean isItagsTextRun) { String res = "-"; String itag = findMatchGroupOne(current, "itag=([0-9]{1,3})\\\\u0026"); if (itag.isEmpty()) { itag = findMatchGroupOne(current, "itag=([0-9]{1,3})$"); } if (isItagsTextRun) { res = findItag(itag); Utils.logger("d", "index: " + i + ", itag: " + itag + " (" + res + ")", DEBUG_TAG); if (YTD.SHOW_ITAGS_AND_NO_SIZE_FOR_DUBUG) { itagsText.add("[" + itag + "]_" + res); } else { itagsText.add(res); } if (itag.equals("139") || itag.equals("140") || itag.equals("141")) { codecs.remove(i); codecs.add(i, "m4a"); } if (itag.equals("171") || itag.equals("172")) { codecs.remove(i); codecs.add(i, "ogg"); } } else { itags.add(Integer.parseInt(itag)); } } private String findItag(String itag) { String res = "-"; if (itag != null) { try { switch (Integer.parseInt(itag)) { // *** url encoded streams *** case 5: res = _FLV_240P; break; case 6: res = _FLV_270P; break; case 17: res = _3GP_144P; break; case 18: res = _MP4_270P_360P; break; case 22: res = _MP4_720P; break; case 34: res = _FLV_360P; break; case 35: res = _FLV_480P; break; case 36: res = _3GP_240P; break; case 37: res = _MP4_1080P; break; case 38: res = _MP4_ORIGINAL; break; case 43: res = _WEBM_360P; break; case 44: res = _WEBM_480P; break; case 45: res = _WEBM_720P; break; case 46: res = _WEBM_1080P; break; case 59: res = _MP4_480P; break; case 78: res = _MP4_360P; break; case 82: res = _MP4_360P_3D; break; case 83: res = _MP4_240P_3D; break; case 84: res = _MP4_720P_3D; break; case 85: res = _MP4_520P_3D; break; case 100: res = _WEBM_360P_3D; break; case 101: res = _WEBM_360P_3D; break; case 102: res = _WEBM_720P_3D; break; // *** adaptive streams *** case 133: res = _VO_MP4_240P; break; case 134: res = _VO_MP4_360P; break; case 135: res = _VO_MP4_480P; break; case 136: res = _VO_MP4_720P; break; case 137: res = _VO_MP4_1080P; break; case 138: res = _VO_MP4_ORIGINAL; break; case 139: res = _AO_M4A_LOW_Q; break; case 140: res = _AO_M4A_MED_Q; break; case 141: res = _AO_M4A_HI_Q; break; case 160: res = _VO_MP4_144P; break; case 171: res = _AO_OGG_MED_Q; break; case 172: res = _AO_OGG_HI_Q; break; case 242: res = _VO_WEBM_240P; break; case 243: res = _VO_WEBM_360P; break; case 244: res = _VO_WEBM_480P; break; case 245: res = _VO_WEBM_480P; break; case 246: res = _VO_WEBM_480P; break; case 247: res = _VO_WEBM_720P; break; case 248: res = _VO_WEBM_1080P; break; case 264: res = _VO_MP4_1080P_HBR; break; default: res = _UNKNOWN; } } catch (NumberFormatException e) { Log.e(DEBUG_TAG, "findItag --> " + e.getMessage()); } } return res; } private String[] generateThumbUrls() { String url1 = "http://i1.ytimg.com/vi/" + videoId + "/mqdefault.jpg"; String url2 = "http://i2.ytimg.com/vi/" + videoId + "/mqdefault.jpg"; String url3 = "http://i3.ytimg.com/vi/" + videoId + "/mqdefault.jpg"; String url4 = "http://i4.ytimg.com/vi/" + videoId + "/mqdefault.jpg"; String[] urls = { url1, url2, url3, url4 }; return urls; } private Bitmap downloadThumbnail(String fileUrl) { InputStream is = null; URL myFileUrl = null; try { myFileUrl = new URL(fileUrl); HttpURLConnection conn = (HttpURLConnection) myFileUrl.openConnection(); conn.setDoInput(true); conn.connect(); is = conn.getInputStream(); return BitmapFactory.decodeStream(is); } catch (IOException e) { Log.e(DEBUG_TAG, "IOException:" + e.getMessage()); return null; } } private void assignBitmapToVideoListThumbnail(String[] url) { Bitmap bm0 = downloadThumbnail(url[0]); if (bm0 != null && !asyncDownload.isCancelled()) { img = bm0; Utils.logger("d", "assigning bitmap from url[0]: " + url[0], DEBUG_TAG); } else { Bitmap bm1 = downloadThumbnail(url[1]); if (bm1 != null && !asyncDownload.isCancelled()) { img = bm1; Utils.logger("d", "assigning bitmap from url[1]: " + url[1], DEBUG_TAG); } else { Bitmap bm2 = downloadThumbnail(url[2]); if (bm2 != null && !asyncDownload.isCancelled()) { img = bm2; Utils.logger("d", "assigning bitmap from url[2]: " + url[2], DEBUG_TAG); } else { Bitmap bm3 = downloadThumbnail(url[3]); if (bm3 != null && !asyncDownload.isCancelled()) { img = bm3; Utils.logger("d", "assigning bitmap from url[3]: " + url[3], DEBUG_TAG); } else { Log.e(DEBUG_TAG, "\nFalling back on asset's placeholder"); InputStream assIs = null; AssetManager assMan = getAssets(); try { assIs = assMan.open("placeholder.png"); } catch (IOException e1) { Log.e(DEBUG_TAG, "downloadThumbnail -> " + e1.getMessage()); } img = BitmapFactory.decodeStream(assIs); } } } } } private void writeThumbToDisk() { File thumbFile = new File(sShare.getDir(YTD.THUMBS_FOLDER, 0), videoId + ".png"); //if (!thumbFile.exists()) { try { FileOutputStream os = new FileOutputStream(thumbFile); img.compress(Bitmap.CompressFormat.PNG, 50, os); } catch (FileNotFoundException e) { Log.e(DEBUG_TAG, "writeThumbToDisk -> " + e.getMessage()); } //} } private void updateInit() { int prefSig = YTD.settings.getInt("APP_SIGNATURE", 0); Utils.logger("d", "prefSig: " + prefSig, DEBUG_TAG); if (prefSig == YTD.SIG_HASH) { Utils.logger("d", "YTD signature in PREFS: update check possile", DEBUG_TAG); if (YTD.settings.getBoolean("autoupdate", false)) { Utils.logger("i", "autoupdate enabled", DEBUG_TAG); SettingsActivity.SettingsFragment.autoUpdate(ShareActivity.this); } } else { Utils.logger("d", "different or null YTD signature. Update check cancelled.", DEBUG_TAG); } } }