diff --git a/app/src/main/java/com/deniscerri/ytdl/database/models/YoutubeGeneratePoTokenItem.kt b/app/src/main/java/com/deniscerri/ytdl/database/models/YoutubeGeneratePoTokenItem.kt new file mode 100644 index 00000000..fcadc5c7 --- /dev/null +++ b/app/src/main/java/com/deniscerri/ytdl/database/models/YoutubeGeneratePoTokenItem.kt @@ -0,0 +1,8 @@ +package com.deniscerri.ytdl.database.models + +data class YoutubeGeneratePoTokenItem( + var enabled: Boolean, + var clients: MutableList, + var poTokens: MutableList, + var visitorData: String +) \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdl/database/models/YoutubePlayerClientItem.kt b/app/src/main/java/com/deniscerri/ytdl/database/models/YoutubePlayerClientItem.kt index 8e5d089b..37747d64 100644 --- a/app/src/main/java/com/deniscerri/ytdl/database/models/YoutubePlayerClientItem.kt +++ b/app/src/main/java/com/deniscerri/ytdl/database/models/YoutubePlayerClientItem.kt @@ -5,8 +5,7 @@ data class YoutubePlayerClientItem( var poTokens: MutableList, var enabled: Boolean = true, var useOnlyPoToken: Boolean = false, - var urlRegex: MutableList = mutableListOf(), - var autoGenerated: Boolean = false + var urlRegex: MutableList = mutableListOf() ) data class YoutubePoTokenItem( diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/adapter/PlaylistAdapter.kt b/app/src/main/java/com/deniscerri/ytdl/ui/adapter/PlaylistAdapter.kt index 675a1ef2..f46d614c 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/adapter/PlaylistAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/adapter/PlaylistAdapter.kt @@ -67,6 +67,7 @@ class PlaylistAdapter(onItemClickListener: OnItemClickListener, activity: Activi card.findViewById(R.id.title).text = item!!.title card.findViewById(R.id.author).text = item.author card.findViewById(R.id.duration).text = item.duration + card.findViewById(R.id.index).text = ((item.playlistIndex ?: (position + 1))).toString() // CHECKBOX ---------------------------------- val check = card.findViewById(R.id.checkBox) diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/adapter/YoutubePlayerClientAdapter.kt b/app/src/main/java/com/deniscerri/ytdl/ui/adapter/YoutubePlayerClientAdapter.kt index 46d582f6..38cb4d7d 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/adapter/YoutubePlayerClientAdapter.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/adapter/YoutubePlayerClientAdapter.kt @@ -91,8 +91,6 @@ class YoutubePlayerClientAdapter(onItemClickListener: OnItemClickListener, activ title.alpha = if (item.enabled) 1f else 0.3f content.alpha = if (item.enabled) 1f else 0.3f - card.findViewById(R.id.autoGeneratedNewPipe).isVisible = item.autoGenerated - val switch = card.findViewById(R.id.materialSwitch) switch.isChecked = item.enabled switch.setOnClickListener { diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/CutVideoBottomSheetDialog.kt b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/CutVideoBottomSheetDialog.kt index 414235b9..46626490 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/CutVideoBottomSheetDialog.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/downloadcard/CutVideoBottomSheetDialog.kt @@ -150,12 +150,12 @@ class CutVideoBottomSheetDialog(private val _item: DownloadItem? = null, private startTextInput = view.findViewById(R.id.from_textinput_edittext) startTextInput.keyListener = DigitsKeyListener.getInstance("0123456789:.") startTextInput.imeOptions = EditorInfo.IME_ACTION_DONE - startTextInput.inputType = EditorInfo.TYPE_NUMBER_FLAG_DECIMAL + startTextInput.inputType = EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS startTextInput.maxLines = 1 endTextInput = view.findViewById(R.id.to_textinput_edittext) endTextInput.keyListener = DigitsKeyListener.getInstance("0123456789:.") endTextInput.imeOptions = EditorInfo.IME_ACTION_DONE - endTextInput.inputType = EditorInfo.TYPE_NUMBER_FLAG_DECIMAL + endTextInput.inputType = EditorInfo.TYPE_TEXT_FLAG_NO_SUGGESTIONS endTextInput.maxLines = 1 cancelBtn = view.findViewById(R.id.cancelButton) diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/GeneralSettingsFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/GeneralSettingsFragment.kt index 6fbd0d66..8ffc696c 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/GeneralSettingsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/GeneralSettingsFragment.kt @@ -1,27 +1,19 @@ package com.deniscerri.ytdl.ui.more.settings +import android.annotation.SuppressLint import android.content.ComponentName import android.content.Context import android.content.DialogInterface import android.content.Intent import android.content.SharedPreferences import android.content.pm.PackageManager -import android.content.res.Resources.Theme import android.net.Uri import android.os.Bundle import android.os.PowerManager import android.provider.Settings -import android.view.MenuInflater -import android.view.MenuItem import androidx.activity.result.contract.ActivityResultContracts -import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate -import androidx.appcompat.widget.PopupMenu -import androidx.core.content.res.ResourcesCompat import androidx.core.os.LocaleListCompat -import androidx.core.text.HtmlCompat -import androidx.core.text.parseAsHtml -import androidx.core.view.forEach import androidx.navigation.fragment.findNavController import androidx.preference.EditTextPreference import androidx.preference.ListPreference @@ -34,13 +26,11 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.work.WorkInfo import androidx.work.WorkManager -import com.deniscerri.ytdl.MainActivity import com.deniscerri.ytdl.R import com.deniscerri.ytdl.databinding.NavOptionsItemBinding import com.deniscerri.ytdl.ui.adapter.NavBarOptionsAdapter import com.deniscerri.ytdl.util.NavbarUtil import com.deniscerri.ytdl.util.ThemeUtil -import com.deniscerri.ytdl.util.ThemeUtil.getThemeColor import com.deniscerri.ytdl.util.UiUtil import com.deniscerri.ytdl.util.UpdateUtil import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -54,6 +44,7 @@ class GeneralSettingsFragment : BaseSettingsFragment() { private var updateUtil: UpdateUtil? = null private var activeDownloadCount = 0 + @SuppressLint("BatteryLife") override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.general_preferences, rootKey) NavbarUtil.init(requireContext()) @@ -82,7 +73,7 @@ class GeneralSettingsFragment : BaseSettingsFragment() { findPreference("label_visibility")?.apply { isVisible = !resources.getBoolean(R.bool.uses_side_nav) - setOnPreferenceChangeListener { preference, newValue -> + setOnPreferenceChangeListener { _, _ -> ThemeUtil.recreateMain() true } @@ -98,9 +89,9 @@ class GeneralSettingsFragment : BaseSettingsFragment() { val options = NavbarUtil.getNavBarItems(requireContext()) val optionsRecycler = binding.findViewById(R.id.options_recycler) - var adapter : NavBarOptionsAdapter? = null; + val adapter : NavBarOptionsAdapter? - val onitemClick = object: NavBarOptionsAdapter.OnItemClickListener { + val onItemClick = object: NavBarOptionsAdapter.OnItemClickListener { override fun onNavBarOptionDeselected(item: NavOptionsItemBinding) { optionsRecycler.findViewHolderForLayoutPosition(0)?.apply { (this as NavBarOptionsAdapter.NavBarOptionsViewHolder).apply { @@ -113,7 +104,7 @@ class GeneralSettingsFragment : BaseSettingsFragment() { adapter = NavBarOptionsAdapter( options.toMutableList(), NavbarUtil.getStartFragmentId(requireContext()), - onitemClick + onItemClick ) val itemTouchCallback = object : ItemTouchHelper.Callback() { @@ -257,20 +248,12 @@ class GeneralSettingsFragment : BaseSettingsFragment() { } } - findPreference("piped_instance")?.setOnPreferenceClickListener { - UiUtil.showPipedInstancesDialog(requireActivity(), preferences.getString("piped_instance", "")!!){ - editor.putString("piped_instance", it) - editor.apply() - } - true - } - findPreference("hide_thumbnails")?.apply { values.filter { it.isNotBlank() }.apply { summary = joinToString(", ") { entries[entryValues.indexOf(it)] } } setOnPreferenceChangeListener { _, newValues -> - (newValues as Set).filter { it.isNotBlank() }.apply { + (newValues as Set<*>).map { it as String }.filter { it.isNotBlank() }.apply { summary = joinToString(", ") { entries[entryValues.indexOf(it)] } } true @@ -282,7 +265,7 @@ class GeneralSettingsFragment : BaseSettingsFragment() { summary = joinToString(", ") { entries[entryValues.indexOf(it)] } } setOnPreferenceChangeListener { _, newValues -> - (newValues as Set).filter { it.isNotBlank() }.apply { + (newValues as Set<*>).map { it as String }.filter { it.isNotBlank() }.apply { summary = joinToString(", ") { entries[entryValues.indexOf(it)] } } true @@ -360,7 +343,7 @@ class GeneralSettingsFragment : BaseSettingsFragment() { if (newValues.size == entries.size) { summary = "${s}\n[${getString(R.string.all)}]" }else if (newValues.isNotEmpty()) { - val indexes = newValues.mapIndexed { index, _ -> index } + val indexes = List(newValues.size) { index -> index } summary = "${s}\n[${entries.filterIndexed { index, _ -> indexes.contains(index) }.joinToString(", ")}]" }else{ summary = s @@ -390,18 +373,9 @@ class GeneralSettingsFragment : BaseSettingsFragment() { super.onResume() } - private fun restartApp() { - val intent = Intent(requireContext(), MainActivity::class.java) - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - startActivity(intent) - requireActivity().finishAffinity() - activity?.finishAffinity() - } - private var displayOverAppsResultLauncher = registerForActivityResult( ActivityResultContracts.StartActivityForResult() - ) { result -> + ) { _ -> findNavController().popBackStack(R.id.appearanceSettingsFragment, false) } diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/advanced/AdvancedSettingsFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/advanced/AdvancedSettingsFragment.kt index 75d23c8b..4f9cc864 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/advanced/AdvancedSettingsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/advanced/AdvancedSettingsFragment.kt @@ -39,51 +39,9 @@ class AdvancedSettingsFragment : BaseSettingsFragment() { false } - val newPipeUtil = NewPipeUtil(requireContext()) - - findPreference("use_newpipe_potoken")?.apply { - fun getValues() : Triple { - val gvs = prefs.getString("newpipe_gvs_potoken", "")!! - val player = prefs.getString("newpipe_player_potoken", "")!! - val visitorData = prefs.getString("newpipe_visitordata", "")!! - - return Triple(gvs, player, visitorData) - } - - fun updateSummary() : String { - val values = getValues() - val gvs = values.first - val player = values.second - val visitorData = values.third - - return if (visitorData.isNotBlank()) { - "PO Token (GVS): ${gvs.take(10)}...\nPO Token (Player): ${player.take(10)}...\nVisitor Data: ${visitorData.take(10)}..." - }else { - "" - } - } - - summary = updateSummary() - - setOnPreferenceClickListener { - if (this.isChecked) { - this.isChecked = false - UiUtil.showGenericConfirmDialog(requireContext(), getString(R.string.use_newpipe_potoken), getString(R.string.use_newpipe_potoken_warning)) { - editor.putBoolean("use_cookies", false) - editor.putBoolean("use_newpipe_token", true) - editor.apply() - this.isChecked = true - lifecycleScope.launch { - summary = getString(R.string.loading) - withContext(Dispatchers.IO) { - newPipeUtil.testRun() - } - summary = updateSummary() - } - } - } - false - } + findPreference("generate_po_tokens")?.setOnPreferenceClickListener { + findNavController().navigate(R.id.generateYoutubePoTokensFragment) + false } val formatImportanceAudio: Preference? = findPreference("format_importance_audio") diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/advanced/GenerateYoutubePoTokensFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/advanced/GenerateYoutubePoTokensFragment.kt new file mode 100644 index 00000000..37dd2eed --- /dev/null +++ b/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/advanced/GenerateYoutubePoTokensFragment.kt @@ -0,0 +1,152 @@ +package com.deniscerri.ytdl.ui.more.settings.advanced + +import android.content.SharedPreferences +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope +import androidx.preference.PreferenceManager +import com.afollestad.materialdialogs.utils.MDUtil.getStringArray +import com.deniscerri.ytdl.R +import com.deniscerri.ytdl.database.models.YoutubeGeneratePoTokenItem +import com.deniscerri.ytdl.database.models.YoutubePoTokenItem +import com.deniscerri.ytdl.ui.more.settings.SettingsActivity +import com.deniscerri.ytdl.util.Extensions.getIDFromYoutubeURL +import com.deniscerri.ytdl.util.UiUtil +import com.deniscerri.ytdl.util.extractors.potoken.PoTokenGenerator +import com.google.android.material.button.MaterialButton +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.google.android.material.materialswitch.MaterialSwitch +import com.google.android.material.snackbar.Snackbar +import com.google.android.material.textfield.TextInputLayout +import com.google.gson.Gson +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class GenerateYoutubePoTokensFragment : Fragment() { + private lateinit var settingsActivity: SettingsActivity + private lateinit var preferences: SharedPreferences + private lateinit var configuration : MutableList + private lateinit var poTokenGenerator: PoTokenGenerator + + private val sampleURL = "https://www.youtube.com/watch?v=aqz-KE-bpKQ" // Big Buck Bunny + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + settingsActivity = activity as SettingsActivity + settingsActivity.changeTopAppbarTitle(getString(R.string.generate_potokens)) + preferences = PreferenceManager.getDefaultSharedPreferences(requireContext()) + poTokenGenerator = PoTokenGenerator() + return inflater.inflate(R.layout.fragment_generate_youtube_po_token, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + configuration = Gson().fromJson( + preferences.getString("youtube_generated_po_tokens", "[]"), + Array::class.java + ).toMutableList() + + initWeb() + } + + private fun initWeb() { + val conf = configuration.find { it.clients.any { it2 -> it2.contains("web") } } + ?: YoutubeGeneratePoTokenItem(false, mutableListOf("mweb"), mutableListOf(), "") + + + val switch = requireView().findViewById(R.id.web_client_switch) + val gvs = requireView().findViewById(R.id.content_gvs) + val player = requireView().findViewById(R.id.content_player) + val visitorData = requireView().findViewById(R.id.content_visitordata) + + val playerClientDiv = requireView().findViewById(R.id.playerclient_div) + val playerClientText = requireView().findViewById(R.id.content_playerclient) + + val regenerate = requireView().findViewById(R.id.regenerate_webview_potokens) + + switch.isChecked = conf.enabled + + fun setValues(conf: YoutubeGeneratePoTokenItem) { + gvs.text = conf.poTokens.find { it.context == "gvs" }?.token ?: "" + player.text = conf.poTokens.find { it.context == "player" }?.token ?: "" + visitorData.text = conf.visitorData + playerClientText.text = conf.clients.joinToString(", ") + } + + setValues(conf) + + val clicker = View.OnClickListener { + UiUtil.copyToClipboard((it as TextView).text.toString(), requireActivity()) + } + + gvs.setOnClickListener(clicker) + player.setOnClickListener(clicker) + visitorData.setOnClickListener(clicker) + + playerClientDiv.setOnClickListener { + val webClients = requireContext().getStringArray(R.array.web_player_clients) + + val selectedItems = webClients.map { + conf.clients.contains(it) + }.toBooleanArray() + + MaterialAlertDialogBuilder(requireContext()) + .setTitle(getString(R.string.player_client)) + .setMultiChoiceItems(webClients, selectedItems) { _, which, isChecked -> + selectedItems[which] = isChecked + } + .setPositiveButton(R.string.ok) { _, _ -> + val newValues = webClients + .filterIndexed { index, _ -> selectedItems[index] } + .toMutableSet() + + configuration.remove(conf) + conf.clients.clear() + conf.clients.addAll(newValues) + configuration.add(conf) + preferences.edit().putString("youtube_generated_po_tokens", Gson().toJson(configuration).toString()).apply() + setValues(conf) + } + .setNegativeButton(R.string.cancel, null) + .show() + } + + regenerate.setOnClickListener { + lifecycleScope.launch { + val res = withContext(Dispatchers.IO) { + poTokenGenerator.getWebClientPoToken(sampleURL.getIDFromYoutubeURL()) + } + + if (res == null) { + Snackbar.make(requireView(), getString(R.string.network_error), Snackbar.LENGTH_LONG).show() + }else{ + configuration.remove(conf) + conf.poTokens.clear() + conf.poTokens.add(YoutubePoTokenItem("gvs", res.streamingDataPoToken ?: "")) + conf.poTokens.add(YoutubePoTokenItem("player", res.playerRequestPoToken)) + conf.visitorData = res.visitorData + + configuration.add(conf) + setValues(conf) + preferences.edit().putString("youtube_generated_po_tokens", Gson().toJson(configuration).toString()).apply() + } + } + } + + switch.setOnClickListener { + configuration.remove(conf) + conf.enabled = switch.isEnabled + configuration.add(conf) + preferences.edit().putString("youtube_generated_po_tokens", Gson().toJson(configuration).toString()).apply() + regenerate.performClick() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/advanced/YoutubePlayerClientFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/advanced/YoutubePlayerClientFragment.kt index ef8339f5..f6ef6815 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/advanced/YoutubePlayerClientFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/advanced/YoutubePlayerClientFragment.kt @@ -22,6 +22,7 @@ import androidx.core.view.children import androidx.core.view.isVisible import androidx.core.widget.doOnTextChanged import androidx.fragment.app.Fragment +import androidx.navigation.fragment.findNavController import androidx.preference.PreferenceManager import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager @@ -94,7 +95,7 @@ class YoutubePlayerClientFragment : Fragment(), YoutubePlayerClientAdapter.OnIte } ) } - + checkNoResults() dragHandle.setOnClickListener { diff --git a/app/src/main/java/com/deniscerri/ytdl/util/Extensions.kt b/app/src/main/java/com/deniscerri/ytdl/util/Extensions.kt index 334ab027..baf63b22 100644 --- a/app/src/main/java/com/deniscerri/ytdl/util/Extensions.kt +++ b/app/src/main/java/com/deniscerri/ytdl/util/Extensions.kt @@ -575,4 +575,21 @@ object Extensions { else "%(section_title&{} - |)s$this" } } + + fun String.getIDFromYoutubeURL() : String { + var el: Array = + this.split("/".toRegex()).dropLastWhile { it.isEmpty() } + .toTypedArray() + var query = el[el.size - 1] + if (query!!.contains("watch?v=")) { + query = query.substring(8) + } + el = query.split("&".toRegex()).dropLastWhile { it.isEmpty() } + .toTypedArray() + query = el[0] + el = query!!.split("\\?".toRegex()).dropLastWhile { it.isEmpty() } + .toTypedArray() + query = el[0] + return query!! + } } \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt b/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt index 8947f311..b0d9c12c 100644 --- a/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdl/util/UiUtil.kt @@ -69,7 +69,6 @@ import com.deniscerri.ytdl.ui.downloadcard.VideoCutListener import com.deniscerri.ytdl.util.Extensions.enableTextHighlight import com.deniscerri.ytdl.util.Extensions.getMediaDuration import com.deniscerri.ytdl.util.Extensions.toStringDuration -import com.deniscerri.ytdl.util.extractors.PipedApiUtil import com.deniscerri.ytdl.util.extractors.YTDLPUtil import com.google.android.material.badge.BadgeDrawable import com.google.android.material.badge.BadgeUtils @@ -2030,81 +2029,6 @@ object UiUtil { } - fun showPipedInstancesDialog(context: Activity, currentInstance: String, instanceSelected: (f: String) -> Unit){ - val builder = MaterialAlertDialogBuilder(context) - builder.setTitle(context.getString(R.string.piped_instance)) - val view = context.layoutInflater.inflate(R.layout.filename_template_dialog, null) - val editText = view.findViewById(R.id.filename_edittext) - view.findViewById(R.id.filename).apply { - hint = context.getString(R.string.piped_instance) - endIconMode = END_ICON_NONE - } - editText.setText(currentInstance) - editText.setSelection(editText.text.length) - builder.setView(view) - builder.setPositiveButton( - context.getString(R.string.ok) - ) { _: DialogInterface?, _: Int -> - instanceSelected(editText.text.toString()) - } - - // handle the negative button of the alert dialog - builder.setNegativeButton( - context.getString(R.string.cancel) - ) { _: DialogInterface?, _: Int -> } - - builder.setNeutralButton("?") { _: DialogInterface?, _: Int -> - val browserIntent = - Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/TeamPiped/Piped/wiki/Instances")) - context.startActivity(browserIntent) - } - - view.findViewById(R.id.suggested).visibility = View.GONE - - val dialog = builder.create() - dialog.show() - val imm = context.getSystemService(AppCompatActivity.INPUT_METHOD_SERVICE) as InputMethodManager - editText!!.postDelayed({ - editText.requestFocus() - imm.showSoftInput(editText, 0) - }, 300) - - //handle suggestion chips - CoroutineScope(Dispatchers.IO).launch { - val chipGroup = view.findViewById(R.id.filename_suggested_chipgroup) - val chips = mutableListOf() - val instances = PipedApiUtil(context).getPipedInstances().ifEmpty { return@launch } - instances.forEach { s -> - val tmp = context.layoutInflater.inflate(R.layout.filter_chip, chipGroup, false) as Chip - tmp.text = s - - tmp.setOnClickListener { - val c = it as Chip - c.toggle() - editText.setText(c.text.toString()) - editText.setSelection(c.text.length) - } - - chips.add(tmp) - } - withContext(Dispatchers.Main){ - view.findViewById(R.id.suggested).visibility = View.VISIBLE - chips.forEach { - it.isChecked = editText.text == it.text - chipGroup!!.addView(it) - } - - editText.doOnTextChanged { text, start, before, count -> - chips.forEach { - it.isChecked = editText.text == it.text - } - } - } - } - - dialog.getButton(AlertDialog.BUTTON_NEUTRAL).gravity = Gravity.START - } - private fun showGeneratedCommand(context: Activity, preferences: SharedPreferences, command: String) { val builder = MaterialAlertDialogBuilder(context) builder.setTitle(context.getString(R.string.command)) diff --git a/app/src/main/java/com/deniscerri/ytdl/util/extractors/PipedApiUtil.kt b/app/src/main/java/com/deniscerri/ytdl/util/extractors/PipedApiUtil.kt deleted file mode 100644 index 2e957b9e..00000000 --- a/app/src/main/java/com/deniscerri/ytdl/util/extractors/PipedApiUtil.kt +++ /dev/null @@ -1,336 +0,0 @@ -package com.deniscerri.ytdl.util.extractors - -import android.content.Context -import android.content.SharedPreferences -import android.text.Html -import android.util.Log -import androidx.preference.PreferenceManager -import com.deniscerri.ytdl.database.models.ChapterItem -import com.deniscerri.ytdl.database.models.Format -import com.deniscerri.ytdl.database.models.ResultItem -import com.deniscerri.ytdl.database.viewmodel.ResultViewModel -import com.deniscerri.ytdl.util.Extensions.toStringDuration -import com.google.gson.Gson -import com.yausername.youtubedl_android.YoutubeDLException -import kotlinx.coroutines.delay -import org.json.JSONException -import org.json.JSONObject -import java.util.Locale -import kotlin.coroutines.cancellation.CancellationException - -class PipedApiUtil(context: Context) { - private var sharedPreferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) - private val countryCode = sharedPreferences.getString("locale", "")!!.ifEmpty { "US" } - private val defaultPipedURL = "https://pipedapi.kavin.rocks/" - private val pipedURL = sharedPreferences.getString("piped_instance", "")!!.ifEmpty { defaultPipedURL }.removeSuffix("/") - - fun getPipedInstances() : List { - kotlin.runCatching { - val res = NetworkUtil.genericArrayRequest("https://piped-instances.kavin.rocks/") - val list = mutableListOf() - for (i in 0 until res.length()) { - val element = res.getJSONObject(i) - list.add(element.getString("api_url")) - } - return list - } - return listOf() - } - - fun getVideoData(url : String) : Result> { - val id = getIDFromYoutubeURL(url) - val res = NetworkUtil.genericRequest("$pipedURL/streams/$id") - if (res.length() == 0) { - return Result.failure(Throwable()) - } - - val vid = createVideoFromPipedJSON(res, url) ?: return Result.failure(Throwable()) - return Result.success(listOf(vid)) - } - - fun getFormats(url: String) : Result > { - try { - val id = getIDFromYoutubeURL(url) - val res = NetworkUtil.genericRequest("$pipedURL/streams/$id") - if (res.length() == 0) { - return Result.failure(Throwable()) - }else { - val item = createVideoFromPipedJSON(res, "https://youtube.com/watch?v=$id", true) - return Result.success(item!!.formats) - } - - }catch(e: Exception) { - println(e) - if (e is CancellationException) throw e - return Result.failure(e) - } - } - - fun getFormatsForAll(urls: List, progress: (progress: ResultViewModel.MultipleFormatProgress) -> Unit) : Result>> { - return kotlin.runCatching { - val formatCollection = mutableListOf>() - urls.forEach { url -> - val id = getIDFromYoutubeURL(url) - val res = NetworkUtil.genericRequest("$pipedURL/streams/$id") - createVideoFromPipedJSON(res, url).apply { - formatCollection.add(this!!.formats) - progress( - ResultViewModel.MultipleFormatProgress(url, this.formats) - ) - } - } - - return Result.success(formatCollection) - }.onFailure { - return Result.failure(it) - } - } - - @Throws(JSONException::class) - fun search(query: String): Result> { - val items = arrayListOf() - val data = NetworkUtil.genericRequest("$pipedURL/search?q=$query&filter=videos®ion=${countryCode}") - val dataArray = data.getJSONArray("items") - if (dataArray.length() == 0) return Result.failure(Throwable()) - for (i in 0 until dataArray.length()) { - val element = dataArray.getJSONObject(i) - if (element.getInt("duration") == -1) continue - element.put("uploader", element.getString("uploaderName")) - val v = createVideoFromPipedJSON(element, "https://youtube.com" + element.getString("url")) - if (v == null || v.thumb.isEmpty()) { - continue - } - items.add(v) - } - return Result.success(items) - } - - @Throws(JSONException::class) - fun searchMusic(query: String): Result> { - val items = arrayListOf() - val data = NetworkUtil.genericRequest("$pipedURL/search?q=$query=&filter=music_songs®ion=${countryCode}") - val dataArray = data.getJSONArray("items") - if (dataArray.length() == 0) return Result.failure(Throwable()) - for (i in 0 until dataArray.length()) { - val element = dataArray.getJSONObject(i) - if (element.getInt("duration") == -1) continue - element.put("uploader", element.getString("uploaderName")) - val v = createVideoFromPipedJSON(element, "https://youtube.com" + element.getString("url")) - if (v == null || v.thumb.isEmpty()) { - continue - } - items.add(v) - } - return Result.success(items) - } - - fun getStreamingUrlAndChapters(url: String) : Result, List?>> { - val id = getIDFromYoutubeURL(url) - val res = NetworkUtil.genericRequest("$pipedURL/streams/$id") - if (res.length() == 0) { - throw Exception() - }else{ - val item = createVideoFromPipedJSON(res, url) - if (item!!.urls.isBlank()) return Result.failure(Throwable()) - - val urls = item.urls.split(",") - val chapters = item.chapters - return Result.success(Pair(urls, chapters)) - } - } - - suspend fun getPlaylistData(id: String, progress: (pagedResults: MutableList) -> Unit) : Result> { - val totalItems = mutableListOf() - val nextPageToken = "" - var playlistName = "" - - while (true) { - val items = mutableListOf() - - var url = "" - url = if (nextPageToken.isBlank()) "$pipedURL/playlists/$id" - else """$pipedURL/nextpage/playlists/$id?nextpage=${ - nextPageToken.replace( - "&prettyPrint", - "%26prettyPrint" - ) - }""" - - println(url) - - val res = NetworkUtil.genericRequest(url) - if (!res.has("relatedStreams")) throw Exception() - - val dataArray = res.getJSONArray("relatedStreams") - val nextpage = res.getString("nextpage") - val isMixPlaylist = nextPageToken.isBlank() && res.getInt("videos") < 0 - if (isMixPlaylist) throw YoutubeDLException("This playlist type is unviewable.") - - for (i in 0 until dataArray.length()) { - kotlin.runCatching { - val obj = dataArray.getJSONObject(i) - createVideoFromPipedJSON( - obj, - "https://youtube.com" + obj.getString("url") - )?.apply { - playlistTitle = playlistName.ifEmpty { runCatching { res.getString("name") }.getOrElse { "" } } - playlistURL = "https://www.youtube.com/playlist?list=$id" - items.add(this) - if (playlistTitle.isNotBlank() && playlistName.isNotBlank()) { - playlistName = playlistTitle - } - } - } - } - - progress(items) - delay(1000) - totalItems.addAll(items) - if (nextpage == "null") break - } - - return Result.success(totalItems) - } - - - fun getTrending(): ArrayList { - val items = arrayListOf() - val url = "$pipedURL/trending?region=${countryCode}" - val res = NetworkUtil.genericArrayRequest(url) - for (i in 0 until res.length()) { - val element = res.getJSONObject(i) - if (element.getInt("duration") < 0) continue - element.put("uploader", element.getString("uploaderName")) - val v = createVideoFromPipedJSON(element, "https://youtube.com" + element.getString("url")) - if (v == null || v.thumb.isEmpty()) continue - items.add(v) - } - return items - } - - fun getChannelData( - url: String, - progress: (pagedResults: MutableList) -> Unit - ): Result> { - return Result.failure(Throwable("Not yet implemented / supported?")) - } - - private fun createVideoFromPipedJSON(obj: JSONObject, url: String, ignoreFormatPreference : Boolean = false): ResultItem? { - var video: ResultItem? = null - try { - val id = getIDFromYoutubeURL(url) - val title = Html.fromHtml(obj.getString("title").toString()).toString() - val author = try { - Html.fromHtml(obj.getString("uploader").toString()).toString() - }catch (e: Exception){ - Html.fromHtml(obj.getString("uploaderName").toString()).toString() - }.removeSuffix(" - Topic") - - val duration = obj.getInt("duration").toStringDuration(Locale.US) - val thumb = "https://i.ytimg.com/vi/$id/hqdefault.jpg" - val formats : ArrayList = ArrayList() - - if(sharedPreferences.getString("formats_source", "yt-dlp") == "piped" || ignoreFormatPreference){ - if (obj.has("audioStreams")){ - val formatsInJSON = obj.getJSONArray("audioStreams") - for (f in 0 until formatsInJSON.length()){ - val format = formatsInJSON.getJSONObject(f) - if (format.getInt("bitrate") == 0) continue - val formatObj = Gson().fromJson(format.toString(), Format::class.java) - try{ - formatObj.acodec = format.getString("codec") - formatObj.asr = format.getString("quality") - if (! format.getString("audioTrackName").equals("null", ignoreCase = true)){ - formatObj.format_note = format.getString("audioTrackName") + " Audio, " + formatObj.format_note - }else{ - formatObj.format_note = formatObj.format_note + " Audio" - } - if (!formatObj.tbr.isNullOrBlank()){ - formatObj.tbr = (formatObj.tbr!!.toInt() / 1000).toString() + "k" - } - - }catch (e: Exception) { - e.printStackTrace() - } - formats.add(formatObj) - } - - } - - if (obj.has("videoStreams")){ - val formatsInJSON = obj.getJSONArray("videoStreams") - for (f in 0 until formatsInJSON.length()){ - val format = formatsInJSON.getJSONObject(f) - if (format.getInt("bitrate") == 0) continue - val formatObj = Gson().fromJson(format.toString(), Format::class.java) - try{ - formatObj.vcodec = format.getString("codec") - if (!formatObj.tbr.isNullOrBlank()){ - formatObj.tbr = (formatObj.tbr!!.toInt() / 1000).toString() + "k" - } - }catch (e: Exception) { - e.printStackTrace() - } - formats.add(formatObj) - } - - } - formats.groupBy { it.format_id }.forEach { - if (it.value.count() > 1) { - it.value.filter { f-> !f.format_note.contains("original", true) }.forEachIndexed { index, format -> format.format_id = format.format_id.split("-")[0] + "-${index}" } - val defaultLang = it.value.find { f -> f.format_note.contains("original", true) } - defaultLang?.format_id = (defaultLang?.format_id?.split("-")?.get(0) ?: "") + "-${it.value.size-1}" - } - } - formats.sortByDescending { it.filesize } - } - - val chapters = ArrayList() - if (obj.has("chapters") && obj.getJSONArray("chapters").length() > 0){ - val chaptersJArray = obj.getJSONArray("chapters") - for (c in 0 until chaptersJArray.length()){ - val chapter = chaptersJArray.getJSONObject(c) - val end = if (c == chaptersJArray.length() - 1) obj.getInt("duration") else chaptersJArray.getJSONObject(c+1).getInt("start") - val item = ChapterItem(chapter.getInt("start").toLong(), end.toLong(), chapter.getString("title")) - chapters.add(item) - } - } - - video = ResultItem(0, - url, - title, - author, - duration, - thumb, - "youtube", - "", - formats, - if (obj.has("hls") && obj.getString("hls") != "null") obj.getString("hls") else "", - chapters - ) - } catch (e: Exception) { - Log.e("PipedAPIUtil", e.toString()) - } - return video - } - - private fun getIDFromYoutubeURL(inputQuery: String) : String { - var el: Array = - inputQuery.split("/".toRegex()).dropLastWhile { it.isEmpty() } - .toTypedArray() - var query = el[el.size - 1] - if (query!!.contains("watch?v=")) { - query = query.substring(8) - } - el = query.split("&".toRegex()).dropLastWhile { it.isEmpty() } - .toTypedArray() - query = el[0] - el = query!!.split("\\?".toRegex()).dropLastWhile { it.isEmpty() } - .toTypedArray() - query = el[0] - return query!! - } - - - -} \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdl/util/extractors/YTDLPUtil.kt b/app/src/main/java/com/deniscerri/ytdl/util/extractors/YTDLPUtil.kt index a116a5f3..ba711df6 100644 --- a/app/src/main/java/com/deniscerri/ytdl/util/extractors/YTDLPUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdl/util/extractors/YTDLPUtil.kt @@ -17,6 +17,7 @@ import com.deniscerri.ytdl.database.models.ChapterItem import com.deniscerri.ytdl.database.models.DownloadItem import com.deniscerri.ytdl.database.models.Format import com.deniscerri.ytdl.database.models.ResultItem +import com.deniscerri.ytdl.database.models.YoutubeGeneratePoTokenItem import com.deniscerri.ytdl.database.models.YoutubePlayerClientItem import com.deniscerri.ytdl.database.viewmodel.DownloadViewModel import com.deniscerri.ytdl.database.viewmodel.ResultViewModel @@ -635,6 +636,7 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co if (aria2) { request.addOption("--downloader", "libaria2c.so") //request.addOption("--external-downloader-args", "aria2c:\"--summary-interval=1\"") + request.addOption("--external-downloader-args", "aria2c:\"--check-certificate=false\"") } val concurrentFragments = sharedPreferences.getInt("concurrent_fragments", 1) @@ -1296,18 +1298,23 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co } } - if (sharedPreferences.getBoolean("use_newpipe_potoken", false)) { - val visitorData = sharedPreferences.getString("newpipe_visitordata", "")!! - if (visitorData.isNotBlank()) { - playerClients.add("web") - sharedPreferences.getString("newpipe_gvs_potoken", "")?.apply { - if (this.isNotBlank()) poTokens.add("web.gvs+$this") + val generatedPoTokensRaw = sharedPreferences.getString("youtube_generated_po_tokens", "[]") + val generatedPoTokens = Gson().fromJson(generatedPoTokensRaw,Array::class.java).toMutableList() + if (generatedPoTokens.isNotEmpty()) { + for (value in generatedPoTokens) { + if (value.enabled) { + for (cl in value.clients) { + playerClients.add(cl) + for (pt in value.poTokens) { + if (pt.token.isNotBlank()) { + poTokens.add("${cl}.${pt.context}+${pt.token}") + } + } + } + + extractorArgs.add("player-skip=webpage,configs") + extractorArgs.add("visitor_data=${value.visitorData}") } - sharedPreferences.getString("newpipe_player_potoken", "")?.apply { - if (this.isNotBlank()) poTokens.add("web.player+$this") - } - extractorArgs.add("player-skip=webpage,configs") - extractorArgs.add("visitor_data=$visitorData") } } diff --git a/app/src/main/java/com/deniscerri/ytdl/util/extractors/newpipe/NewPipeUtil.kt b/app/src/main/java/com/deniscerri/ytdl/util/extractors/newpipe/NewPipeUtil.kt index 69fea561..fc12d2f2 100644 --- a/app/src/main/java/com/deniscerri/ytdl/util/extractors/newpipe/NewPipeUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdl/util/extractors/newpipe/NewPipeUtil.kt @@ -8,8 +8,9 @@ import com.deniscerri.ytdl.database.models.ChapterItem import com.deniscerri.ytdl.database.models.Format import com.deniscerri.ytdl.database.models.ResultItem import com.deniscerri.ytdl.database.viewmodel.ResultViewModel +import com.deniscerri.ytdl.util.Extensions.getIDFromYoutubeURL import com.deniscerri.ytdl.util.Extensions.toStringDuration -import com.deniscerri.ytdl.util.extractors.newpipe.potoken.NewPipePoTokenGenerator +import com.deniscerri.ytdl.util.extractors.potoken.PoTokenGenerator import com.google.gson.Gson import okhttp3.OkHttpClient import org.json.JSONException @@ -35,16 +36,10 @@ class NewPipeUtil(context: Context) { private var sharedPreferences: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) private val countryCode = sharedPreferences.getString("locale", "")!!.ifEmpty { "US" } private val language = sharedPreferences.getString("app_language", "")!!.ifEmpty { "en" } - private val testURL = "https://www.youtube.com/watch?v=aqz-KE-bpKQ" //bbb init { NewPipe.init(NewPipeDownloaderImpl(OkHttpClient.Builder()), Localization(language, countryCode)) - YoutubeStreamExtractor.setPoTokenProvider(NewPipePoTokenGenerator()) - } - - fun testRun() { - getVideoData(testURL) - return + YoutubeStreamExtractor.setPoTokenProvider(PoTokenGenerator()) } fun getVideoData(url : String) : Result> { @@ -288,7 +283,7 @@ class NewPipeUtil(context: Context) { private fun createVideoFromStreamInfoItem(stream: StreamInfoItem, url: String) : ResultItem? { var video: ResultItem? = null try { - val id = getIDFromYoutubeURL(url) + val id = url.getIDFromYoutubeURL() val title = stream.name val author = stream.uploaderName.removeSuffix(" - Topic") val duration = stream.duration.toInt().toStringDuration(Locale.US) @@ -316,7 +311,7 @@ class NewPipeUtil(context: Context) { private fun createVideoFromStream(stream: StreamInfo, url: String, ignoreFormatPreference : Boolean = false): ResultItem? { var video: ResultItem? = null try { - val id = getIDFromYoutubeURL(url) + val id = url.getIDFromYoutubeURL() val title = stream.name val author = stream.uploaderName.removeSuffix(" - Topic") val duration = stream.duration.toInt().toStringDuration(Locale.US) @@ -420,27 +415,4 @@ class NewPipeUtil(context: Context) { } return video } - - private fun getIDFromYoutubeURL(inputQuery: String) : String { - var el: Array = - inputQuery.split("/".toRegex()).dropLastWhile { it.isEmpty() } - .toTypedArray() - var query = el[el.size - 1] - if (query!!.contains("watch?v=")) { - query = query.substring(8) - } - el = query.split("&".toRegex()).dropLastWhile { it.isEmpty() } - .toTypedArray() - query = el[0] - el = query!!.split("\\?".toRegex()).dropLastWhile { it.isEmpty() } - .toTypedArray() - query = el[0] - return query!! - } - - private fun generatePoToken(url: String) : PoTokenResult? { - val generator = NewPipePoTokenGenerator() - val id = getIDFromYoutubeURL(url) - return generator.getWebClientPoToken(id) - } } \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdl/util/extractors/newpipe/potoken/PoTokenException.kt b/app/src/main/java/com/deniscerri/ytdl/util/extractors/potoken/PoTokenException.kt similarity index 86% rename from app/src/main/java/com/deniscerri/ytdl/util/extractors/newpipe/potoken/PoTokenException.kt rename to app/src/main/java/com/deniscerri/ytdl/util/extractors/potoken/PoTokenException.kt index 61c2f4ca..5a819929 100644 --- a/app/src/main/java/com/deniscerri/ytdl/util/extractors/newpipe/potoken/PoTokenException.kt +++ b/app/src/main/java/com/deniscerri/ytdl/util/extractors/potoken/PoTokenException.kt @@ -1,4 +1,4 @@ -package com.deniscerri.ytdl.util.extractors.newpipe.potoken +package com.deniscerri.ytdl.util.extractors.potoken class PoTokenException(message: String) : Exception(message) diff --git a/app/src/main/java/com/deniscerri/ytdl/util/extractors/newpipe/potoken/NewPipePoTokenGenerator.kt b/app/src/main/java/com/deniscerri/ytdl/util/extractors/potoken/PoTokenGenerator.kt similarity index 83% rename from app/src/main/java/com/deniscerri/ytdl/util/extractors/newpipe/potoken/NewPipePoTokenGenerator.kt rename to app/src/main/java/com/deniscerri/ytdl/util/extractors/potoken/PoTokenGenerator.kt index 4cde37a6..3aaf84e0 100644 --- a/app/src/main/java/com/deniscerri/ytdl/util/extractors/newpipe/potoken/NewPipePoTokenGenerator.kt +++ b/app/src/main/java/com/deniscerri/ytdl/util/extractors/potoken/PoTokenGenerator.kt @@ -1,4 +1,4 @@ -package com.deniscerri.ytdl.util.extractors.newpipe.potoken +package com.deniscerri.ytdl.util.extractors.potoken import android.os.Handler import android.os.Looper @@ -7,22 +7,16 @@ import android.webkit.CookieManager import androidx.preference.PreferenceManager import com.deniscerri.ytdl.App import com.deniscerri.ytdl.BuildConfig -import com.deniscerri.ytdl.database.models.YoutubePlayerClientItem -import com.deniscerri.ytdl.database.models.YoutubePoTokenItem -import com.google.gson.Gson -import kotlinx.coroutines.Dispatchers +import com.deniscerri.ytdl.util.extractors.potoken.webview.PoTokenWebView import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import kotlinx.coroutines.withContext import org.schabi.newpipe.extractor.NewPipe import org.schabi.newpipe.extractor.services.youtube.InnertubeClientRequestInfo import org.schabi.newpipe.extractor.services.youtube.PoTokenProvider import org.schabi.newpipe.extractor.services.youtube.PoTokenResult import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper -class NewPipePoTokenGenerator : PoTokenProvider { - val TAG = NewPipePoTokenGenerator::class.simpleName +class PoTokenGenerator : PoTokenProvider { + val TAG = PoTokenGenerator::class.simpleName private val supportsWebView by lazy { runCatching { CookieManager.getInstance() }.isSuccess } private object WebPoTokenGenLock @@ -39,15 +33,6 @@ class NewPipePoTokenGenerator : PoTokenProvider { val result = kotlin.runCatching { getWebClientPoToken(videoId, false) } - - result.getOrNull()?.apply { - val preferences = PreferenceManager.getDefaultSharedPreferences(App.instance) - val editor = preferences.edit() - editor.putString("newpipe_gvs_potoken", this.streamingDataPoToken ?: "") - editor.putString("newpipe_player_potoken", this.playerRequestPoToken) - editor.putString("newpipe_visitordata", this.visitorData) - editor.apply() - } return result.getOrNull() } @@ -84,8 +69,7 @@ class NewPipePoTokenGenerator : PoTokenProvider { webPoTokenGenerator?.let { Handler(Looper.getMainLooper()).post { it.close() } } // create a new webPoTokenGenerator - webPoTokenGenerator = PoTokenWebView - .getNewPoTokenGenerator(App.instance) + webPoTokenGenerator = PoTokenWebView.getNewPoTokenGenerator(App.instance) // The streaming poToken needs to be generated exactly once before generating // any other (player) tokens. diff --git a/app/src/main/java/com/deniscerri/ytdl/util/extractors/newpipe/potoken/JavascriptUtil.kt b/app/src/main/java/com/deniscerri/ytdl/util/extractors/potoken/webview/JavascriptUtil.kt similarity index 97% rename from app/src/main/java/com/deniscerri/ytdl/util/extractors/newpipe/potoken/JavascriptUtil.kt rename to app/src/main/java/com/deniscerri/ytdl/util/extractors/potoken/webview/JavascriptUtil.kt index 0d7e7a06..58f197d6 100644 --- a/app/src/main/java/com/deniscerri/ytdl/util/extractors/newpipe/potoken/JavascriptUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdl/util/extractors/potoken/webview/JavascriptUtil.kt @@ -1,5 +1,6 @@ -package com.deniscerri.ytdl.util.extractors.newpipe.potoken +package com.deniscerri.ytdl.util.extractors.potoken.webview +import com.deniscerri.ytdl.util.extractors.potoken.PoTokenException import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonNull import kotlinx.serialization.json.JsonObject diff --git a/app/src/main/java/com/deniscerri/ytdl/util/extractors/newpipe/potoken/PoTokenWebView.kt b/app/src/main/java/com/deniscerri/ytdl/util/extractors/potoken/webview/PoTokenWebView.kt similarity index 94% rename from app/src/main/java/com/deniscerri/ytdl/util/extractors/newpipe/potoken/PoTokenWebView.kt rename to app/src/main/java/com/deniscerri/ytdl/util/extractors/potoken/webview/PoTokenWebView.kt index 23fd72f0..039058a2 100644 --- a/app/src/main/java/com/deniscerri/ytdl/util/extractors/newpipe/potoken/PoTokenWebView.kt +++ b/app/src/main/java/com/deniscerri/ytdl/util/extractors/potoken/webview/PoTokenWebView.kt @@ -1,4 +1,4 @@ -package com.deniscerri.ytdl.util.extractors.newpipe.potoken +package com.deniscerri.ytdl.util.extractors.potoken.webview import android.content.Context import android.os.Build @@ -18,10 +18,13 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withContext import com.deniscerri.ytdl.BuildConfig -import com.deniscerri.ytdl.util.extractors.newpipe.potoken.JavascriptUtil.parseChallengeData -import com.deniscerri.ytdl.util.extractors.newpipe.potoken.JavascriptUtil.parseIntegrityTokenData -import com.deniscerri.ytdl.util.extractors.newpipe.potoken.JavascriptUtil.stringToU8 -import com.deniscerri.ytdl.util.extractors.newpipe.potoken.JavascriptUtil.u8ToBase64 +import com.deniscerri.ytdl.util.extractors.potoken.BadWebViewException +import com.deniscerri.ytdl.util.extractors.potoken.PoTokenException +import com.deniscerri.ytdl.util.extractors.potoken.buildExceptionForJsError +import com.deniscerri.ytdl.util.extractors.potoken.webview.JavascriptUtil.parseChallengeData +import com.deniscerri.ytdl.util.extractors.potoken.webview.JavascriptUtil.parseIntegrityTokenData +import com.deniscerri.ytdl.util.extractors.potoken.webview.JavascriptUtil.stringToU8 +import com.deniscerri.ytdl.util.extractors.potoken.webview.JavascriptUtil.u8ToBase64 import okhttp3.Headers.Companion.toHeaders import okhttp3.OkHttpClient import okhttp3.RequestBody.Companion.toRequestBody @@ -39,8 +42,7 @@ class PoTokenWebView private constructor( ) { private val webView = WebView(context) private val scope = MainScope() - private val poTokenContinuations = - Collections.synchronizedMap(ArrayMap>()) + private val poTokenContinuations = Collections.synchronizedMap(ArrayMap>()) private val exceptionHandler = CoroutineExceptionHandler { _, t -> onInitializationErrorCloseAndCancel(t) } @@ -60,13 +62,6 @@ class PoTokenWebView private constructor( // so that we can run async functions and get back the result webView.addJavascriptInterface(this, JS_INTERFACE) - val preferences = PreferenceManager.getDefaultSharedPreferences(context) - preferences.getString("api_key", "")?.apply { - if (this.isNotBlank()) { - GOOGLE_API_KEY = this - } - } - webView.webChromeClient = object : WebChromeClient() { override fun onConsoleMessage(m: ConsoleMessage): Boolean { if (m.message().contains("Uncaught")) { @@ -330,7 +325,7 @@ class PoTokenWebView private constructor( companion object { private const val TAG = "PoTokenWebView" - //libretube api key, if user has his own api key his will be used + //libretube api key private var GOOGLE_API_KEY = "AIzaSyDyT5W0Jh49F30Pqqtyfdf7pDLFKLJoAnw" private const val REQUEST_KEY = "O43z0dpjhgX20SCx4KAo" private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + diff --git a/app/src/main/res/drawable/baseline_stars_24.xml b/app/src/main/res/drawable/baseline_stars_24.xml new file mode 100644 index 00000000..4543dbdf --- /dev/null +++ b/app/src/main/res/drawable/baseline_stars_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/layout/cut_video_sheet.xml b/app/src/main/res/layout/cut_video_sheet.xml index de0a2914..fb99df7d 100644 --- a/app/src/main/res/layout/cut_video_sheet.xml +++ b/app/src/main/res/layout/cut_video_sheet.xml @@ -1,5 +1,6 @@ - + android:textStyle="bold" + tools:ignore="HardcodedText" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +