diff --git a/.github/ISSUE_TEMPLATE/application-bug.yml b/.github/ISSUE_TEMPLATE/application-bug.yml
index f3590067..931db3bd 100644
--- a/.github/ISSUE_TEMPLATE/application-bug.yml
+++ b/.github/ISSUE_TEMPLATE/application-bug.yml
@@ -80,13 +80,13 @@ body:
label: Acknowledgements
description: Your issue will be closed if you haven't done these steps.
options:
- - label: I am sure my issue is related to the app and **NOT some extension**.
- required: true
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue.
required: true
- label: I have written a short but informative title.
required: true
- label: I have updated the app to pre-release version **[Latest](https://github.com/recloudstream/cloudstream/releases)**.
required: true
+ - label: If related to a provider, I have checked the site and it works, but not the app.
+ required: true
- label: I will fill out all of the requested information in this form.
required: true
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index b56cdf8e..250734cd 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -2,7 +2,7 @@ blank_issues_enabled: false
contact_links:
- name: Request a new provider or report bug with an existing provider
url: https://github.com/recloudstream
- about: EXTREMELY IMPORTANT - Please do not report any provider bugs here or request new providers. This repository does not contain any providers. Please find the appropriate repository and report your issue there or join the discord.
+ about: Please do not report any provider bugs here or request new providers. This repository does not contain any providers. Please find the appropriate repository and report your issue there or join the discord.
- name: Discord
url: https://discord.gg/5Hus6fM
about: Join our discord for faster support on smaller issues.
diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml
index e18daebb..9c35ba56 100644
--- a/.github/ISSUE_TEMPLATE/feature-request.yml
+++ b/.github/ISSUE_TEMPLATE/feature-request.yml
@@ -27,7 +27,9 @@ body:
label: Acknowledgements
description: Your issue will be closed if you haven't done these steps.
options:
- - label: My suggestion is **NOT** about adding a new provider
- required: true
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue.
- required: true
\ No newline at end of file
+ required: true
+ - label: I have written a short but informative title.
+ required: true
+ - label: I will fill out all of the requested information in this form.
+ required: true
diff --git a/.github/locales.py b/.github/locales.py
index a74d7258..7d6d6b90 100644
--- a/.github/locales.py
+++ b/.github/locales.py
@@ -1,7 +1,6 @@
import re
import glob
import requests
-import os
import lxml.etree as ET # builtin library doesn't preserve comments
@@ -54,16 +53,11 @@ for file in glob.glob(f"{XML_NAME}*/strings.xml"):
try:
tree = ET.parse(file)
for child in tree.getroot():
- if not child.text:
- continue
if child.text.startswith("\\@string/"):
print(f"[{file}] fixing {child.attrib['name']}")
child.text = child.text.replace("\\@string/", "@string/")
with open(file, 'wb') as fp:
fp.write(b'\n')
tree.write(fp, encoding="utf-8", method="xml", pretty_print=True, xml_declaration=False)
- # Remove trailing new line to be consistent with weblate
- fp.seek(-1, os.SEEK_END)
- fp.truncate()
except ET.ParseError as ex:
print(f"[{file}] {ex}")
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index d7c08c9c..c5c0ff3b 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -4,16 +4,17 @@
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index d0c86bab..c2ba2907 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,6 +1,5 @@
import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties
import org.jetbrains.dokka.gradle.DokkaTask
-import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.archivesName
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import java.io.ByteArrayOutputStream
import java.net.URL
@@ -42,8 +41,8 @@ android {
}*/
signingConfigs {
- if (prereleaseStoreFile != null) {
- create("prerelease") {
+ create("prerelease") {
+ if (prereleaseStoreFile != null) {
storeFile = file(prereleaseStoreFile)
storePassword = System.getenv("SIGNING_STORE_PASSWORD")
keyAlias = System.getenv("SIGNING_KEY_ALIAS")
@@ -60,8 +59,8 @@ android {
minSdk = 21
targetSdk = 33 /* Android 14 is Fu*ked
^ https://developer.android.com/about/versions/14/behavior-changes-14#safer-dynamic-code-loading*/
- versionCode = 64
- versionName = "4.4.0"
+ versionCode = 63
+ versionName = "4.3.2"
resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}")
resValue("string", "commit_hash", "git rev-parse --short HEAD".execute() ?: "")
@@ -71,9 +70,9 @@ android {
val localProperties = gradleLocalProperties(rootDir)
buildConfigField(
- "long",
- "BUILD_DATE",
- "${System.currentTimeMillis()}"
+ "String",
+ "BUILDDATE",
+ "new java.text.SimpleDateFormat(\"yyyy-MM-dd HH:mm\").format(new java.util.Date(" + System.currentTimeMillis() + "L));"
)
buildConfigField(
"String",
@@ -124,11 +123,7 @@ android {
resValue("bool", "is_prerelease", "true")
buildConfigField("boolean", "BETA", "true")
applicationIdSuffix = ".prerelease"
- if (signingConfigs.names.contains("prerelease")) {
- signingConfig = signingConfigs.getByName("prerelease")
- } else {
- logger.warn("No prerelease signing config!")
- }
+ signingConfig = signingConfigs.getByName("prerelease")
versionNameSuffix = "-PRE"
versionCode = (System.currentTimeMillis() / 60000).toInt()
}
@@ -144,7 +139,7 @@ android {
abortOnError = false
checkReleaseBuilds = false
}
-
+
buildFeatures {
buildConfig = true
}
@@ -159,24 +154,24 @@ repositories {
dependencies {
// Testing
testImplementation("junit:junit:4.13.2")
- testImplementation("org.json:json:20240303")
+ testImplementation("org.json:json:20231013")
androidTestImplementation("androidx.test:core")
- implementation("androidx.test.ext:junit-ktx:1.2.1")
- androidTestImplementation("androidx.test.ext:junit:1.2.1")
- androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
+ implementation("androidx.test.ext:junit-ktx:1.1.5")
+ androidTestImplementation("androidx.test.ext:junit:1.1.5")
+ androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
// Android Core & Lifecycle
- implementation("androidx.core:core-ktx:1.13.1")
- implementation("androidx.appcompat:appcompat:1.7.0")
+ implementation("androidx.core:core-ktx:1.12.0")
+ implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.navigation:navigation-ui-ktx:2.7.7")
- implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.8.3")
- implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.3")
+ implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0")
+ implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
implementation("androidx.navigation:navigation-fragment-ktx:2.7.7")
// Design & UI
implementation("jp.wasabeef:glide-transformations:4.3.0")
implementation("androidx.preference:preference-ktx:1.2.1")
- implementation("com.google.android.material:material:1.12.0")
+ implementation("com.google.android.material:material:1.11.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
@@ -186,9 +181,9 @@ dependencies {
implementation("com.github.bumptech.glide:okhttp3-integration:4.16.0")
// For KSP -> Official Annotation Processors are Not Yet Supported for KSP
- ksp("dev.zacsweers.autoservice:auto-service-ksp:1.2.0")
- implementation("com.google.guava:guava:33.2.1-android")
- implementation("dev.zacsweers.autoservice:auto-service-ksp:1.2.0")
+ ksp("dev.zacsweers.autoservice:auto-service-ksp:1.1.0")
+ implementation("com.google.guava:guava:32.1.3-android")
+ implementation("dev.zacsweers.autoservice:auto-service-ksp:1.1.0")
// Media 3 (ExoPlayer)
implementation("androidx.media3:media3-ui:1.1.1")
@@ -204,9 +199,9 @@ dependencies {
// PlayBack
implementation("com.jaredrummler:colorpicker:1.1.0") // Subtitle Color Picker
implementation("com.github.recloudstream:media-ffmpeg:1.1.0") // Custom FF-MPEG Lib for Audio Codecs
- implementation("com.github.teamnewpipe:NewPipeExtractor:176da72") /* For Trailers
+ implementation("com.github.teamnewpipe:NewPipeExtractor:6dc25f7") /* For Trailers
^ Update to Latest Commits if Trailers Misbehave, github.com/TeamNewPipe/NewPipeExtractor/commits/dev */
- implementation("com.github.albfernandez:juniversalchardet:2.5.0") // Subtitle Decoding
+ implementation("com.github.albfernandez:juniversalchardet:2.4.0") // Subtitle Decoding
// Crash Reports (AcraApplication.kt)
implementation("ch.acra:acra-core:5.11.3")
@@ -217,17 +212,18 @@ dependencies {
implementation("androidx.palette:palette-ktx:1.0.0") // Palette For Images -> Colors
implementation("androidx.tvprovider:tvprovider:1.0.0")
implementation("com.github.discord:OverlappingPanels:0.1.5") // Gestures
- implementation("androidx.biometric:biometric:1.2.0-alpha05") // Fingerprint Authentication
+ implementation ("androidx.biometric:biometric:1.2.0-alpha05") // Fingerprint Authentication
implementation("com.github.rubensousa:previewseekbar-media3:1.1.1.0") // SeekBar Preview
- implementation("io.github.g0dkar:qrcode-kotlin:4.2.0") // QR code for PIN Auth on TV
// Extensions & Other Libs
- implementation("org.mozilla:rhino:1.7.15") // run JavaScript
+ implementation("org.mozilla:rhino:1.7.13") /* run JavaScript
+ ^ Don't Bump RhinoJS to 1.7.14,`NoClassDefFoundError` Occurs and Trailers won't play (even with Desugaring)
+ NewPipeExtractor Issue */
implementation("me.xdrop:fuzzywuzzy:1.4.0") // Library/Ext Searching with Levenshtein Distance
implementation("com.github.LagradOst:SafeFile:0.0.6") // To Prevent the URI File Fu*kery
implementation("org.conscrypt:conscrypt-android:2.5.2") // To Fix SSL Fu*kery on Android 9
- implementation("com.uwetrottmann.tmdb2:tmdb-java:2.11.0") // TMDB API v3 Wrapper Made with RetroFit
- coreLibraryDesugaring("com.android.tools:desugar_jdk_libs_nio:2.0.4") //nio flavor needed for NewPipeExtractor
+ implementation("com.uwetrottmann.tmdb2:tmdb-java:2.10.0") // TMDB API v3 Wrapper Made with RetroFit
+ coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.1") /* JSON Parser
^ Don't Bump Jackson above 2.13.1 , Crashes on Android TV's and FireSticks that have Min API
Level 25 or Less. */
@@ -236,46 +232,18 @@ dependencies {
implementation("androidx.work:work-runtime:2.9.0")
implementation("androidx.work:work-runtime-ktx:2.9.0")
implementation("com.github.Blatzar:NiceHttp:0.4.11") // HTTP Lib
-
- implementation(project(":library") {
- // There does not seem to be a good way of getting the android flavor.
- val isDebug = gradle.startParameter.taskRequests.any { task ->
- task.args.any { arg ->
- arg.contains("debug", true)
- }
- }
-
- this.extra.set("isDebug", isDebug)
- })
}
-tasks.register("androidSourcesJar") {
+tasks.register("androidSourcesJar", Jar::class) {
archiveClassifier.set("sources")
from(android.sourceSets.getByName("main").java.srcDirs) // Full Sources
}
-tasks.register("copyJar") {
- from(
- "build/intermediates/compile_app_classes_jar/prereleaseDebug",
- "../library/build/libs"
- )
- into("build/app-classes")
- include("classes.jar", "library-jvm*.jar")
- // Remove the version
- rename("library-jvm.*.jar", "library-jvm.jar")
-}
-
-// Merge the app classes and the library classes into classes.jar
-tasks.register("makeJar") {
- // Duplicates cause hard to catch errors, better to fail at compile time.
- duplicatesStrategy = DuplicatesStrategy.FAIL
- dependsOn(tasks.getByName("copyJar"))
- from(
- zipTree("build/app-classes/classes.jar"),
- zipTree("build/app-classes/library-jvm.jar")
- )
- destinationDirectory.set(layout.buildDirectory)
- archivesName = "classes"
+// For GradLew Plugin
+tasks.register("makeJar", Copy::class) {
+ from("build/intermediates/compile_app_classes_jar/prereleaseDebug")
+ into("build")
+ include("classes.jar")
}
tasks.withType {
diff --git a/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt
index c7f02baf..faacdf50 100644
--- a/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt
+++ b/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt
@@ -154,7 +154,7 @@ class ExampleInstrumentedTest {
fun providerCorrectHomepage() {
runBlocking {
getAllProviders().toList().amap { api ->
- TestingUtils.testHomepage(api, TestingUtils.Logger())
+ TestingUtils.testHomepage(api, ::println)
}
}
println("Done providerCorrectHomepage")
@@ -166,6 +166,7 @@ class ExampleInstrumentedTest {
TestingUtils.getDeferredProviderTests(
this,
getAllProviders(),
+ ::println
) { _, _ -> }
}
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 888be999..a23ef725 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -97,7 +97,7 @@
-->
Unit)) :
ACRA.errorReporter.handleException(error)
try {
PrintStream(errorFile).use { ps ->
- ps.println("Currently loading extension: ${PluginManager.currentlyLoading ?: "none"}")
- ps.println("Fatal exception on thread ${thread.name} (${thread.id})")
+ ps.println(String.format("Currently loading extension: ${PluginManager.currentlyLoading ?: "none"}"))
+ ps.println(
+ String.format(
+ "Fatal exception on thread %s (%d)",
+ thread.name,
+ thread.id
+ )
+ )
error.printStackTrace(ps)
}
} catch (ignored: FileNotFoundException) {
@@ -101,6 +105,7 @@ class AcraApplication : Application() {
override fun onCreate() {
super.onCreate()
+ //NativeCrashHandler.initCrashHandler()
ExceptionHandler(filesDir.resolve("last_error")) {
val intent = context!!.packageManager.getLaunchIntentForPackage(context!!.packageName)
startActivity(Intent.makeRestartActivityTask(intent!!.component))
@@ -146,7 +151,6 @@ class AcraApplication : Application() {
get() = _context?.get()
private set(value) {
_context = WeakReference(value)
- setContext(WeakReference(value))
}
fun getKeyClass(path: String, valueType: Class): T? {
diff --git a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt
index ee3a5d12..4dc78dc7 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt
@@ -5,7 +5,6 @@ import android.app.Activity
import android.app.PictureInPictureParams
import android.content.Context
import android.content.pm.PackageManager
-import android.content.res.Configuration
import android.content.res.Resources
import android.os.Build
import android.util.DisplayMetrics
@@ -30,14 +29,13 @@ import com.google.android.material.chip.ChipGroup
import com.google.android.material.navigationrail.NavigationRailView
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
-import com.lagradost.cloudstream3.MainActivity.Companion.resumeApps
import com.lagradost.cloudstream3.databinding.ToastBinding
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.player.PlayerEventType
import com.lagradost.cloudstream3.ui.result.ResultFragment
import com.lagradost.cloudstream3.ui.result.UiText
import com.lagradost.cloudstream3.ui.settings.Globals.updateTv
-import com.lagradost.cloudstream3.utils.AppContextUtils.isRtl
+import com.lagradost.cloudstream3.utils.AppUtils.isRtl
import com.lagradost.cloudstream3.utils.DataStoreHelper
import com.lagradost.cloudstream3.utils.Event
import com.lagradost.cloudstream3.utils.UIHelper
@@ -165,7 +163,7 @@ object CommonActivity {
val toast = Toast(act)
toast.duration = duration ?: Toast.LENGTH_SHORT
toast.setGravity(Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM, 0, 5.toPx)
- toast.view = binding.root //fixme Find an alternative using default Toasts since custom toasts are deprecated and won't appear with api30 set as minSDK version.
+ toast.view = binding.root
currentToast = toast
toast.show()
@@ -277,35 +275,12 @@ object CommonActivity {
}
}
- fun updateTheme(act: Activity) {
- val settingsManager = PreferenceManager.getDefaultSharedPreferences(act)
- if (settingsManager
- .getString(act.getString(R.string.app_theme_key), "AmoledLight") == "System"
- && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- loadThemes(act)
- }
- }
-
- private fun mapSystemTheme(act: Activity): Int {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- val currentNightMode =
- act.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
- return when (currentNightMode) {
- Configuration.UI_MODE_NIGHT_NO -> R.style.LightMode // Night mode is not active, we're using the light theme
- else -> R.style.AppTheme // Night mode is active, we're using dark theme
- }
- } else {
- return R.style.AppTheme
- }
- }
-
fun loadThemes(act: Activity?) {
if (act == null) return
val settingsManager = PreferenceManager.getDefaultSharedPreferences(act)
val currentTheme =
when (settingsManager.getString(act.getString(R.string.app_theme_key), "AmoledLight")) {
- "System" -> mapSystemTheme(act)
"Black" -> R.style.AppTheme
"Light" -> R.style.LightMode
"Amoled" -> R.style.AmoledMode
@@ -376,8 +351,8 @@ object CommonActivity {
currentLook = currentLook.parent as? View ?: break
}*/
- private fun View.hasContent(): Boolean {
- return isShown && when (this) {
+ private fun View.hasContent() : Boolean {
+ return isShown && when(this) {
//is RecyclerView -> this.childCount > 0
is ViewGroup -> this.childCount > 0
else -> true
@@ -488,6 +463,20 @@ object CommonActivity {
fun onKeyDown(act: Activity?, keyCode: Int, event: KeyEvent?) {
+ //println("Keycode: $keyCode")
+ //showToast(
+ // this,
+ // "Got Keycode $keyCode | ${KeyEvent.keyCodeToString(keyCode)} \n ${event?.action}",
+ // Toast.LENGTH_LONG
+ //)
+
+ // Tested keycodes on remote:
+ // KeyEvent.KEYCODE_MEDIA_FAST_FORWARD
+ // KeyEvent.KEYCODE_MEDIA_REWIND
+ // KeyEvent.KEYCODE_MENU
+ // KeyEvent.KEYCODE_MEDIA_NEXT
+ // KeyEvent.KEYCODE_MEDIA_PREVIOUS
+ // KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
// 149 keycode_numpad 5
when (keyCode) {
diff --git a/app/src/main/java/com/lagradost/cloudstream3/DownloaderTestImpl.kt b/app/src/main/java/com/lagradost/cloudstream3/DownloaderTestImpl.kt
index 8da7ca38..0a2db2bd 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/DownloaderTestImpl.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/DownloaderTestImpl.kt
@@ -2,7 +2,6 @@ package com.lagradost.cloudstream3
import okhttp3.OkHttpClient
import okhttp3.RequestBody
-import okhttp3.RequestBody.Companion.toRequestBody
import org.schabi.newpipe.extractor.downloader.Downloader
import org.schabi.newpipe.extractor.downloader.Request
import org.schabi.newpipe.extractor.downloader.Response
@@ -11,7 +10,7 @@ import java.util.concurrent.TimeUnit
class DownloaderTestImpl private constructor(builder: OkHttpClient.Builder) : Downloader() {
- private val client: OkHttpClient = builder.readTimeout(30, TimeUnit.SECONDS).build()
+ private val client: OkHttpClient
override fun execute(request: Request): Response {
val httpMethod: String = request.httpMethod()
val url: String = request.url()
@@ -19,7 +18,7 @@ class DownloaderTestImpl private constructor(builder: OkHttpClient.Builder) : Do
val dataToSend: ByteArray? = request.dataToSend()
var requestBody: RequestBody? = null
if (dataToSend != null) {
- requestBody = dataToSend.toRequestBody(null, 0, dataToSend.size)
+ requestBody = RequestBody.create(null, dataToSend)
}
val requestBuilder: okhttp3.Request.Builder = okhttp3.Request.Builder()
.method(httpMethod, requestBody).url(url)
@@ -74,4 +73,8 @@ class DownloaderTestImpl private constructor(builder: OkHttpClient.Builder) : Do
return instance
}
}
+
+ init {
+ client = builder.readTimeout(30, TimeUnit.SECONDS).build()
+ }
}
\ No newline at end of file
diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt
similarity index 84%
rename from library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt
rename to app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt
index 50dd667b..273e267b 100644
--- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt
@@ -1,43 +1,49 @@
package com.lagradost.cloudstream3
+import android.annotation.SuppressLint
+import android.content.Context
+import android.net.Uri
+import android.util.Base64.encodeToString
+import androidx.annotation.WorkerThread
+import androidx.preference.PreferenceManager
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.module.kotlin.kotlinModule
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
+import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi
+import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi
+import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.simklApi
import com.lagradost.cloudstream3.syncproviders.SyncIdName
+import com.lagradost.cloudstream3.syncproviders.providers.SimklApi
+import com.lagradost.cloudstream3.ui.player.SubtitleData
+import com.lagradost.cloudstream3.ui.result.ResultViewModel2
import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.utils.AppUtils.toJson
-import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
import com.lagradost.cloudstream3.utils.Coroutines.mainWork
import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf
import com.lagradost.nicehttp.RequestBodyTypes
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody.Companion.toRequestBody
-import java.net.URI
import java.text.SimpleDateFormat
import java.util.*
-import kotlin.io.encoding.Base64
-import kotlin.io.encoding.ExperimentalEncodingApi
import kotlin.math.absoluteValue
+const val USER_AGENT =
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36"
+
+//val baseHeader = mapOf("User-Agent" to USER_AGENT)
+val mapper = JsonMapper.builder().addModule(kotlinModule())
+ .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()!!
+
/**
* Defines the constant for the all languages preference, if this is set then it is
* the equivalent of all languages being set
**/
const val AllLanguagesName = "universal"
-const val USER_AGENT =
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36"
-
-class ErrorLoadingException(message: String? = null) : Exception(message)
-
-//val baseHeader = mapOf("User-Agent" to USER_AGENT)
-val mapper = JsonMapper.builder().addModule(kotlinModule())
- .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()!!
-
object APIHolder {
val unixTime: Long
get() = System.currentTimeMillis() / 1000L
@@ -108,6 +114,16 @@ object APIHolder {
return null
}
+ private fun getLoadResponseIdFromUrl(url: String, apiName: String): Int {
+ return url.replace(getApiFromNameNull(apiName)?.mainUrl ?: "", "").replace("/", "")
+ .hashCode()
+ }
+
+ fun LoadResponse.getId(): Int {
+ // this fixes an issue with outdated api as getLoadResponseIdFromUrl might be fucked
+ return (if (this is ResultViewModel2.LoadResponseFromSearch) this.id else null) ?: getLoadResponseIdFromUrl(url, apiName)
+ }
+
/**
* Gets the website captcha token
* discovered originally by https://github.com/ahmedgamal17
@@ -123,9 +139,10 @@ object APIHolder {
// To get the key
suspend fun getCaptchaToken(url: String, key: String, referer: String? = null): String? {
try {
- val uri = URI.create(url)
- val domain = base64Encode(
+ val uri = Uri.parse(url)
+ val domain = encodeToString(
(uri.scheme + "://" + uri.host + ":443").encodeToByteArray(),
+ 0
).replace("\n", "").replace("=", ".")
val vToken =
@@ -205,15 +222,10 @@ object APIHolder {
} ?: false
val matchingTypes = types?.any { it.name.equals(media.format, true) } == true
- if (lessAccurate) matchingTitles || matchingTypes && matchingYears else matchingTitles && matchingTypes && matchingYears
+ if(lessAccurate) matchingTitles || matchingTypes && matchingYears else matchingTitles && matchingTypes && matchingYears
} ?: return null
- Tracker(
- res.idMal,
- res.id.toString(),
- res.coverImage?.extraLarge ?: res.coverImage?.large,
- res.bannerImage
- )
+ Tracker(res.idMal, res.id.toString(), res.coverImage?.extraLarge ?: res.coverImage?.large, res.bannerImage)
} catch (t: Throwable) {
logError(t)
null
@@ -260,6 +272,165 @@ object APIHolder {
return app.post("https://graphql.anilist.co", requestBody = data)
.parsedSafe()
}
+
+
+ fun Context.getApiSettings(): HashSet {
+ //val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
+
+ val hashSet = HashSet()
+ val activeLangs = getApiProviderLangSettings()
+ val hasUniversal = activeLangs.contains(AllLanguagesName)
+ hashSet.addAll(synchronized(apis) { apis.filter { hasUniversal || activeLangs.contains(it.lang) } }
+ .map { it.name })
+
+ /*val set = settingsManager.getStringSet(
+ this.getString(R.string.search_providers_list_key),
+ hashSet
+ )?.toHashSet() ?: hashSet
+
+ val list = HashSet()
+ for (name in set) {
+ val api = getApiFromNameNull(name) ?: continue
+ if (activeLangs.contains(api.lang)) {
+ list.add(name)
+ }
+ }*/
+ //if (list.isEmpty()) return hashSet
+ //return list
+ return hashSet
+ }
+
+ fun Context.getApiDubstatusSettings(): HashSet {
+ val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
+ val hashSet = HashSet()
+ hashSet.addAll(DubStatus.values())
+ val list = settingsManager.getStringSet(
+ this.getString(R.string.display_sub_key),
+ hashSet.map { it.name }.toMutableSet()
+ ) ?: return hashSet
+
+ val names = DubStatus.values().map { it.name }.toHashSet()
+ //if(realSet.isEmpty()) return hashSet
+
+ return list.filter { names.contains(it) }.map { DubStatus.valueOf(it) }.toHashSet()
+ }
+
+ fun Context.getApiProviderLangSettings(): HashSet {
+ val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
+ val hashSet = hashSetOf(AllLanguagesName) // def is all languages
+// hashSet.add("en") // def is only en
+ val list = settingsManager.getStringSet(
+ this.getString(R.string.provider_lang_key),
+ hashSet
+ )
+
+ if (list.isNullOrEmpty()) return hashSet
+ return list.toHashSet()
+ }
+
+ fun Context.getApiTypeSettings(): HashSet {
+ val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
+ val hashSet = HashSet()
+ hashSet.addAll(TvType.values())
+ val list = settingsManager.getStringSet(
+ this.getString(R.string.search_types_list_key),
+ hashSet.map { it.name }.toMutableSet()
+ )
+
+ if (list.isNullOrEmpty()) return hashSet
+
+ val names = TvType.values().map { it.name }.toHashSet()
+ val realSet = list.filter { names.contains(it) }.map { TvType.valueOf(it) }.toHashSet()
+ if (realSet.isEmpty()) return hashSet
+
+ return realSet
+ }
+
+ fun Context.updateHasTrailers() {
+ LoadResponse.isTrailersEnabled = getHasTrailers()
+ }
+
+ private fun Context.getHasTrailers(): Boolean {
+ val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
+ return settingsManager.getBoolean(this.getString(R.string.show_trailers_key), true)
+ }
+
+ fun Context.filterProviderByPreferredMedia(hasHomePageIsRequired: Boolean = true): List {
+ // We are getting the weirdest crash ever done:
+ // java.lang.ClassCastException: com.lagradost.cloudstream3.TvType cannot be cast to com.lagradost.cloudstream3.TvType
+ // Trying fixing using classloader fuckery
+ val oldLoader = Thread.currentThread().contextClassLoader
+ Thread.currentThread().contextClassLoader = TvType::class.java.classLoader
+
+ val default = TvType.values()
+ .sorted()
+ .filter { it != TvType.NSFW }
+ .map { it.ordinal }
+
+ Thread.currentThread().contextClassLoader = oldLoader
+
+ val defaultSet = default.map { it.toString() }.toSet()
+ val currentPrefMedia = try {
+ PreferenceManager.getDefaultSharedPreferences(this)
+ .getStringSet(this.getString(R.string.prefer_media_type_key), defaultSet)
+ ?.mapNotNull { it.toIntOrNull() ?: return@mapNotNull null }
+ } catch (e: Throwable) {
+ null
+ } ?: default
+ val langs = this.getApiProviderLangSettings()
+ val hasUniversal = langs.contains(AllLanguagesName)
+ val allApis = synchronized(apis) {
+ apis.filter { api -> (hasUniversal || langs.contains(api.lang)) && (api.hasMainPage || !hasHomePageIsRequired) }
+ }
+ return if (currentPrefMedia.isEmpty()) {
+ allApis
+ } else {
+ // Filter API depending on preferred media type
+ allApis.filter { api -> api.supportedTypes.any { currentPrefMedia.contains(it.ordinal) } }
+ }
+ }
+
+ fun Context.filterSearchResultByFilmQuality(data: List): List {
+ // Filter results omitting entries with certain quality
+ if (data.isNotEmpty()) {
+ val filteredSearchQuality = PreferenceManager.getDefaultSharedPreferences(this)
+ ?.getStringSet(getString(R.string.pref_filter_search_quality_key), setOf())
+ ?.mapNotNull { entry ->
+ entry.toIntOrNull() ?: return@mapNotNull null
+ } ?: listOf()
+ if (filteredSearchQuality.isNotEmpty()) {
+ return data.filter { item ->
+ val searchQualVal = item.quality?.ordinal ?: -1
+ //Log.i("filterSearch", "QuickSearch item => ${item.toJson()}")
+ !filteredSearchQuality.contains(searchQualVal)
+ }
+ }
+ }
+ return data
+ }
+
+ fun Context.filterHomePageListByFilmQuality(data: HomePageList): HomePageList {
+ // Filter results omitting entries with certain quality
+ if (data.list.isNotEmpty()) {
+ val filteredSearchQuality = PreferenceManager.getDefaultSharedPreferences(this)
+ ?.getStringSet(getString(R.string.pref_filter_search_quality_key), setOf())
+ ?.mapNotNull { entry ->
+ entry.toIntOrNull() ?: return@mapNotNull null
+ } ?: listOf()
+ if (filteredSearchQuality.isNotEmpty()) {
+ return HomePageList(
+ name = data.name,
+ isHorizontalImages = data.isHorizontalImages,
+ list = data.list.filter { item ->
+ val searchQualVal = item.quality?.ordinal ?: -1
+ //Log.i("filterSearch", "QuickSearch item => ${item.toJson()}")
+ !filteredSearchQuality.contains(searchQualVal)
+ }
+ )
+ }
+ }
+ return data
+ }
}
/*
@@ -448,7 +619,7 @@ abstract class MainAPI {
/**Used for testing and can be used to disable the providers if WebView is not available*/
open val usesWebView = false
- /** Determines which plugin a given provider is from. This is the full path to the plugin. */
+ /** Determines which plugin a given provider is from */
var sourcePlugin: String? = null
open val hasMainPage = false
@@ -482,7 +653,7 @@ abstract class MainAPI {
//emptyList() //
open val mainPage = listOf(MainPageData("", "", false))
- // @WorkerThread
+ @WorkerThread
open suspend fun getMainPage(
page: Int,
request: MainPageRequest,
@@ -490,17 +661,17 @@ abstract class MainAPI {
throw NotImplementedError()
}
- // @WorkerThread
+ @WorkerThread
open suspend fun search(query: String): List? {
throw NotImplementedError()
}
- // @WorkerThread
+ @WorkerThread
open suspend fun quickSearch(query: String): List? {
throw NotImplementedError()
}
- // @WorkerThread
+ @WorkerThread
/**
* Based on data from search() or getMainPage() it generates a LoadResponse,
* basically opening the info page from a link.
@@ -518,13 +689,13 @@ abstract class MainAPI {
* This function might be updated to include exoplayer timestamps etc in the future
* if the need arises.
* */
- // @WorkerThread
+ @WorkerThread
open suspend fun extractorVerifierJob(extractorData: String?) {
throw NotImplementedError()
}
/**Callback is fired once a link is found, will return true if method is executed successfully*/
- // @WorkerThread
+ @WorkerThread
open suspend fun loadLinks(
data: String,
isCasting: Boolean,
@@ -549,18 +720,31 @@ abstract class MainAPI {
}
/** Might need a different implementation for desktop*/
+@SuppressLint("NewApi")
fun base64Decode(string: String): String {
return String(base64DecodeArray(string), Charsets.ISO_8859_1)
}
-@OptIn(ExperimentalEncodingApi::class)
+
+@SuppressLint("NewApi")
fun base64DecodeArray(string: String): ByteArray {
- return Base64.decode(string)
+ return try {
+ android.util.Base64.decode(string, android.util.Base64.DEFAULT)
+ } catch (e: Exception) {
+ Base64.getDecoder().decode(string)
+ }
}
-@OptIn(ExperimentalEncodingApi::class)
+
+@SuppressLint("NewApi")
fun base64Encode(array: ByteArray): String {
- return Base64.encode(array)
+ return try {
+ String(android.util.Base64.encode(array, android.util.Base64.NO_WRAP), Charsets.ISO_8859_1)
+ } catch (e: Exception) {
+ String(Base64.getEncoder().encode(array))
+ }
}
+class ErrorLoadingException(message: String? = null) : Exception(message)
+
fun MainAPI.fixUrlNull(url: String?): String? {
if (url.isNullOrEmpty()) {
return null
@@ -594,6 +778,10 @@ fun sortUrls(urls: Set): List {
return urls.sortedBy { t -> -t.quality }
}
+fun sortSubs(subs: Set): List {
+ return subs.sortedBy { it.name }
+}
+
fun capitalizeString(str: String): String {
return capitalizeStringNullable(str) ?: str
}
@@ -677,12 +865,7 @@ enum class TvType(value: Int?) {
AsianDrama(9),
Live(10),
NSFW(11),
- Others(12),
- Music(13),
- AudioBook(14),
-
- /** Wont load the built in player, make your own interaction */
- CustomMedia(15),
+ Others(12)
}
public enum class AutoDownloadMode(val value: Int) {
@@ -1015,25 +1198,11 @@ interface LoadResponse {
var contentRating: String?
companion object {
- var malIdPrefix = "" //malApi.idPrefix
- var aniListIdPrefix = "" //aniListApi.idPrefix
- var simklIdPrefix = "" //simklApi.idPrefix
+ private val malIdPrefix = malApi.idPrefix
+ private val aniListIdPrefix = aniListApi.idPrefix
+ private val simklIdPrefix = simklApi.idPrefix
var isTrailersEnabled = true
- /**
- * The ID string is a way to keep a collection of services in one single ID using a map
- * This adds a database service (like imdb) to the string and returns the new string.
- */
- fun addIdToString(idString: String?, database: SimklSyncServices, id: String?): String? {
- if (id == null) return idString
- return (readIdFromString(idString) + mapOf(database to id)).toJson()
- }
-
- /** Read the id string to get all other ids */
- fun readIdFromString(idString: String?): Map {
- return tryParseJson(idString) ?: return emptyMap()
- }
-
fun LoadResponse.isMovie(): Boolean {
return this.type.isMovieType() || this is MovieLoadResponse
}
@@ -1057,12 +1226,12 @@ interface LoadResponse {
* Internal helper function to add simkl ids from other databases.
*/
private fun LoadResponse.addSimklId(
- database: SimklSyncServices,
+ database: SimklApi.Companion.SyncServices,
id: String?
) {
normalSafeApiCall {
this.syncData[simklIdPrefix] =
- addIdToString(this.syncData[simklIdPrefix], database, id.toString())
+ SimklApi.addIdToString(this.syncData[simklIdPrefix], database, id.toString())
?: return@normalSafeApiCall
}
}
@@ -1082,28 +1251,28 @@ interface LoadResponse {
fun LoadResponse.getImdbId(): String? {
return normalSafeApiCall {
- readIdFromString(this.syncData[simklIdPrefix])[SimklSyncServices.Imdb]
+ SimklApi.readIdFromString(this.syncData[simklIdPrefix])?.get(SimklApi.Companion.SyncServices.Imdb)
}
}
fun LoadResponse.getTMDbId(): String? {
return normalSafeApiCall {
- readIdFromString(this.syncData[simklIdPrefix])[SimklSyncServices.Tmdb]
+ SimklApi.readIdFromString(this.syncData[simklIdPrefix])?.get(SimklApi.Companion.SyncServices.Tmdb)
}
}
fun LoadResponse.addMalId(id: Int?) {
this.syncData[malIdPrefix] = (id ?: return).toString()
- this.addSimklId(SimklSyncServices.Mal, id.toString())
+ this.addSimklId(SimklApi.Companion.SyncServices.Mal, id.toString())
}
fun LoadResponse.addAniListId(id: Int?) {
this.syncData[aniListIdPrefix] = (id ?: return).toString()
- this.addSimklId(SimklSyncServices.AniList, id.toString())
+ this.addSimklId(SimklApi.Companion.SyncServices.AniList, id.toString())
}
fun LoadResponse.addSimklId(id: Int?) {
- this.addSimklId(SimklSyncServices.Simkl, id.toString())
+ this.addSimklId(SimklApi.Companion.SyncServices.Simkl, id.toString())
}
fun LoadResponse.addImdbUrl(url: String?) {
@@ -1185,7 +1354,7 @@ interface LoadResponse {
fun LoadResponse.addImdbId(id: String?) {
// TODO add imdb sync
- this.addSimklId(SimklSyncServices.Imdb, id)
+ this.addSimklId(SimklApi.Companion.SyncServices.Imdb, id)
}
fun LoadResponse.addTrackId(id: String?) {
@@ -1198,7 +1367,7 @@ interface LoadResponse {
fun LoadResponse.addTMDbId(id: String?) {
// TODO add TMDb sync
- this.addSimklId(SimklSyncServices.Tmdb, id)
+ this.addSimklId(SimklApi.Companion.SyncServices.Tmdb, id)
}
fun LoadResponse.addRating(text: String?) {
@@ -1277,24 +1446,11 @@ fun TvType?.isEpisodeBased(): Boolean {
return (this == TvType.TvSeries || this == TvType.Anime || this == TvType.AsianDrama)
}
+
data class NextAiring(
val episode: Int,
val unixTime: Long,
- val season: Int? = null,
-) {
- /**
- * Secondary constructor for backwards compatibility without season.
- * TODO Remove this constructor after there is a new stable release and extensions are updated to support season.
- */
- constructor(
- episode: Int,
- unixTime: Long,
- ) : this(
- episode,
- unixTime,
- null
- )
-}
+)
/**
* @param season To be mapped with episode season, not shown in UI if displaySeason is defined
@@ -1385,26 +1541,8 @@ data class TorrentLoadResponse(
posterHeaders: Map? = null,
backgroundPosterUrl: String? = null,
) : this(
- name,
- url,
- apiName,
- magnet,
- torrent,
- plot,
- type,
- posterUrl,
- year,
- rating,
- tags,
- duration,
- trailers,
- recommendations,
- actors,
- comingSoon,
- syncData,
- posterHeaders,
- backgroundPosterUrl,
- null
+ name, url, apiName, magnet, torrent, plot, type, posterUrl, year, rating, tags, duration, trailers,
+ recommendations, actors, comingSoon, syncData, posterHeaders, backgroundPosterUrl, null
)
}
@@ -1456,8 +1594,7 @@ data class AnimeLoadResponse(
return this.episodes.maxOf { (_, episodes) ->
episodes.count { episodeData ->
// Prioritize display season as actual season may be something random to fit multiple seasons into one.
- val episodeSeason =
- displayMap[episodeData.season] ?: episodeData.season ?: Int.MIN_VALUE
+ val episodeSeason = displayMap[episodeData.season] ?: episodeData.season ?: Int.MIN_VALUE
// Count all episodes from season 1 to below the current season.
episodeSeason in 1..? = null,
backgroundPosterUrl: String? = null,
) : this(
- engName,
- japName,
- name,
- url,
- apiName,
- type,
- posterUrl,
- year,
- episodes,
- showStatus,
- plot,
- tags,
- synonyms,
- rating,
- duration,
- trailers,
- recommendations,
- actors,
- comingSoon,
- syncData,
- posterHeaders,
- nextAiring,
- seasonNames,
- backgroundPosterUrl,
- null
+ engName, japName, name, url, apiName, type, posterUrl, year, episodes, showStatus, plot, tags,
+ synonyms, rating, duration, trailers, recommendations, actors, comingSoon, syncData, posterHeaders,
+ nextAiring, seasonNames, backgroundPosterUrl, null
)
}
@@ -1650,7 +1765,7 @@ data class MovieLoadResponse(
backgroundPosterUrl: String? = null,
) : this(
name, url, apiName, type, dataUrl, posterUrl, year, plot, rating, tags, duration, trailers,
- recommendations, actors, comingSoon, syncData, posterHeaders, backgroundPosterUrl, null
+ recommendations, actors, comingSoon, syncData, posterHeaders, backgroundPosterUrl,null
)
}
@@ -1700,17 +1815,7 @@ suspend fun MainAPI.newMovieLoadResponse(
builder.initializer()
return builder
}
-/** Episode information that will be passed to LoadLinks function & showed on UI
- * @property data string used as main LoadLinks fun parameter.
- * @property name Name of the Episode.
- * @property season Season number.
- * @property episode Episode number.
- * @property posterUrl URL of Episode's poster image.
- * @property rating Episode rating.
- * @property date Episode air date, see addDate.
- * @property runTime Episode runtime in seconds.
- * @see[addDate]
- * */
+
data class Episode(
var data: String,
var name: String? = null,
@@ -1720,25 +1825,7 @@ data class Episode(
var rating: Int? = null,
var description: String? = null,
var date: Long? = null,
- var runTime: Int? = null,
-) {
- /**
- * Secondary constructor for backwards compatibility without runTime.
- * TODO Remove this constructor after there is a new stable release and extensions are updated to support runTime.
- */
- constructor(
- data: String,
- name: String? = null,
- season: Int? = null,
- episode: Int? = null,
- posterUrl: String? = null,
- rating: Int? = null,
- description: String? = null,
- date: Long? = null,
- ) : this(
- data, name, season, episode, posterUrl, rating, description, date, null
- )
-}
+)
fun Episode.addDate(date: String?, format: String = "yyyy-MM-dd") {
try {
@@ -1780,28 +1867,6 @@ fun MainAPI.newEpisode(
return builder
}
-interface IDownloadableMinimum {
- val url: String
- val referer: String
- val headers: Map
-}
-
-fun IDownloadableMinimum.getId(): Int {
- return url.hashCode()
-}
-
-/**
- * Set of sync services simkl is compatible with.
- * Add more as required: https://simkl.docs.apiary.io/#reference/search/id-lookup/get-items-by-id
- */
-enum class SimklSyncServices(val originalName: String) {
- Simkl("simkl"),
- Imdb("imdb"),
- Tmdb("tmdb"),
- AniList("anilist"),
- Mal("mal"),
-}
-
data class TvSeriesLoadResponse(
override var name: String,
override var url: String,
@@ -1843,8 +1908,7 @@ data class TvSeriesLoadResponse(
return episodes.count { episodeData ->
// Prioritize display season as actual season may be something random to fit multiple seasons into one.
- val episodeSeason =
- displayMap[episodeData.season] ?: episodeData.season ?: Int.MIN_VALUE
+ val episodeSeason = displayMap[episodeData.season] ?: episodeData.season ?: Int.MIN_VALUE
// Count all episodes from season 1 to below the current season.
episodeSeason in 1..? = null,
backgroundPosterUrl: String? = null,
) : this(
- name,
- url,
- apiName,
- type,
- episodes,
- posterUrl,
- year,
- plot,
- showStatus,
- rating,
- tags,
- duration,
- trailers,
- recommendations,
- actors,
- comingSoon,
- syncData,
- posterHeaders,
- nextAiring,
- seasonNames,
- backgroundPosterUrl,
- null
+ name, url, apiName, type, episodes, posterUrl, year, plot, showStatus, rating, tags, duration,
+ trailers, recommendations, actors, comingSoon, syncData, posterHeaders, nextAiring, seasonNames,
+ backgroundPosterUrl, null
)
}
@@ -1962,7 +2007,6 @@ data class AniSearch(
@JsonProperty("extraLarge") var extraLarge: String? = null,
@JsonProperty("large") var large: String? = null,
)
-
data class Title(
@JsonProperty("romaji") var romaji: String? = null,
@JsonProperty("english") var english: String? = null,
diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
index 5408d2a8..67bf19fb 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
@@ -44,6 +44,9 @@ import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.LinearSnapHelper
import androidx.recyclerview.widget.RecyclerView
+import com.fasterxml.jackson.databind.DeserializationFeature
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.google.android.gms.cast.framework.CastContext
import com.google.android.gms.cast.framework.Session
import com.google.android.gms.cast.framework.SessionManager
@@ -56,7 +59,9 @@ import com.google.common.collect.Comparators.min
import com.jaredrummler.android.colorpicker.ColorPickerDialogListener
import com.lagradost.cloudstream3.APIHolder.allProviders
import com.lagradost.cloudstream3.APIHolder.apis
+import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings
import com.lagradost.cloudstream3.APIHolder.initAll
+import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
@@ -68,7 +73,6 @@ import com.lagradost.cloudstream3.CommonActivity.screenHeight
import com.lagradost.cloudstream3.CommonActivity.setActivityInstance
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.CommonActivity.updateLocale
-import com.lagradost.cloudstream3.CommonActivity.updateTheme
import com.lagradost.cloudstream3.databinding.ActivityMainBinding
import com.lagradost.cloudstream3.databinding.ActivityMainTvBinding
import com.lagradost.cloudstream3.databinding.BottomResultviewPreviewBinding
@@ -83,22 +87,20 @@ import com.lagradost.cloudstream3.plugins.PluginManager.loadAllOnlinePlugins
import com.lagradost.cloudstream3.plugins.PluginManager.loadSinglePlugin
import com.lagradost.cloudstream3.receivers.VideoDownloadRestartReceiver
import com.lagradost.cloudstream3.services.SubscriptionWorkManager
-import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.APP_STRING
-import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.APP_STRING_PLAYER
-import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.APP_STRING_REPO
-import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.APP_STRING_RESUME_WATCHING
-import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.APP_STRING_SEARCH
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.OAuth2Apis
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.accountManagers
+import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appString
+import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringPlayer
+import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringRepo
+import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringResumeWatching
+import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringSearch
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.inAppAuths
-import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.localListApi
import com.lagradost.cloudstream3.syncproviders.SyncAPI
import com.lagradost.cloudstream3.ui.APIRepository
import com.lagradost.cloudstream3.ui.SyncWatchType
import com.lagradost.cloudstream3.ui.WatchType
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO
import com.lagradost.cloudstream3.ui.home.HomeViewModel
-import com.lagradost.cloudstream3.ui.library.LibraryViewModel
import com.lagradost.cloudstream3.ui.player.BasicLink
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
import com.lagradost.cloudstream3.ui.player.LinkGenerator
@@ -108,7 +110,6 @@ import com.lagradost.cloudstream3.ui.result.START_ACTION_RESUME_LATEST
import com.lagradost.cloudstream3.ui.result.SyncViewModel
import com.lagradost.cloudstream3.ui.result.setImage
import com.lagradost.cloudstream3.ui.result.setText
-import com.lagradost.cloudstream3.ui.result.setTextHtml
import com.lagradost.cloudstream3.ui.result.txt
import com.lagradost.cloudstream3.ui.search.SearchFragment
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
@@ -121,27 +122,20 @@ import com.lagradost.cloudstream3.ui.settings.SettingsGeneral
import com.lagradost.cloudstream3.ui.setup.HAS_DONE_SETUP_KEY
import com.lagradost.cloudstream3.ui.setup.SetupFragmentExtensions
import com.lagradost.cloudstream3.utils.ApkInstaller
-import com.lagradost.cloudstream3.utils.AppContextUtils.getApiDubstatusSettings
-import com.lagradost.cloudstream3.utils.AppContextUtils.html
-import com.lagradost.cloudstream3.utils.AppContextUtils.isCastApiAvailable
-import com.lagradost.cloudstream3.utils.AppContextUtils.isLtr
-import com.lagradost.cloudstream3.utils.AppContextUtils.isNetworkAvailable
-import com.lagradost.cloudstream3.utils.AppContextUtils.isRtl
-import com.lagradost.cloudstream3.utils.AppContextUtils.loadCache
-import com.lagradost.cloudstream3.utils.AppContextUtils.loadRepository
-import com.lagradost.cloudstream3.utils.AppContextUtils.loadResult
-import com.lagradost.cloudstream3.utils.AppContextUtils.loadSearchResult
-import com.lagradost.cloudstream3.utils.AppContextUtils.setDefaultFocus
-import com.lagradost.cloudstream3.utils.AppContextUtils.updateHasTrailers
-import com.lagradost.cloudstream3.utils.BackPressedCallbackHelper.attachBackPressedCallback
-import com.lagradost.cloudstream3.utils.BackPressedCallbackHelper.detachBackPressedCallback
+import com.lagradost.cloudstream3.utils.AppUtils.html
+import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable
+import com.lagradost.cloudstream3.utils.AppUtils.isLtr
+import com.lagradost.cloudstream3.utils.AppUtils.isNetworkAvailable
+import com.lagradost.cloudstream3.utils.AppUtils.isRtl
+import com.lagradost.cloudstream3.utils.AppUtils.loadCache
+import com.lagradost.cloudstream3.utils.AppUtils.loadRepository
+import com.lagradost.cloudstream3.utils.AppUtils.loadResult
+import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
+import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus
import com.lagradost.cloudstream3.utils.BackupUtils.backup
import com.lagradost.cloudstream3.utils.BackupUtils.setUpBackup
-import com.lagradost.cloudstream3.utils.BiometricAuthenticator.BiometricCallback
-import com.lagradost.cloudstream3.utils.BiometricAuthenticator.biometricPrompt
+import com.lagradost.cloudstream3.utils.BiometricAuthenticator
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.deviceHasPasswordPinLock
-import com.lagradost.cloudstream3.utils.BiometricAuthenticator.isAuthEnabled
-import com.lagradost.cloudstream3.utils.BiometricAuthenticator.promptInfo
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.startBiometricAuthentication
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.Coroutines.main
@@ -153,7 +147,6 @@ import com.lagradost.cloudstream3.utils.DataStoreHelper.migrateResumeWatching
import com.lagradost.cloudstream3.utils.Event
import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
-import com.lagradost.cloudstream3.utils.SnackbarHelper.showSnackbar
import com.lagradost.cloudstream3.utils.UIHelper.changeStatusBarState
import com.lagradost.cloudstream3.utils.UIHelper.checkWrite
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
@@ -165,7 +158,8 @@ import com.lagradost.cloudstream3.utils.UIHelper.requestRW
import com.lagradost.cloudstream3.utils.UIHelper.toPx
import com.lagradost.cloudstream3.utils.USER_PROVIDER_API
import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API
-import com.lagradost.cloudstream3.utils.fcast.FcastManager
+import com.lagradost.nicehttp.Requests
+import com.lagradost.nicehttp.ResponseParser
import com.lagradost.safefile.SafeFile
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
@@ -176,6 +170,7 @@ import java.net.URLDecoder
import java.nio.charset.Charset
import kotlin.math.abs
import kotlin.math.absoluteValue
+import kotlin.reflect.KClass
import kotlin.system.exitProcess
//https://github.com/videolan/vlc-android/blob/3706c4be2da6800b3d26344fc04fab03ffa4b860/application/vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerActivity.kt#L1898
@@ -188,92 +183,117 @@ import kotlin.system.exitProcess
//https://github.com/jellyfin/jellyfin-android/blob/6cbf0edf84a3da82347c8d59b5d5590749da81a9/app/src/main/java/org/jellyfin/mobile/bridge/ExternalPlayer.kt#L225
-class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCallback {
+const val VLC_PACKAGE = "org.videolan.vlc"
+const val MPV_PACKAGE = "is.xyz.mpv"
+const val WEB_VIDEO_CAST_PACKAGE = "com.instantbits.cast.webvideo"
+
+val VLC_COMPONENT = ComponentName(VLC_PACKAGE, "$VLC_PACKAGE.gui.video.VideoPlayerActivity")
+val MPV_COMPONENT = ComponentName(MPV_PACKAGE, "$MPV_PACKAGE.MPVActivity")
+
+//TODO REFACTOR AF
+open class ResultResume(
+ val packageString: String,
+ val action: String = Intent.ACTION_VIEW,
+ val position: String? = null,
+ val duration: String? = null,
+ var launcher: ActivityResultLauncher? = null,
+) {
+ val defaultTime = -1L
+
+ val lastId get() = "${packageString}_last_open_id"
+ suspend fun launch(id: Int?, callback: suspend Intent.() -> Unit) {
+ val intent = Intent(action)
+
+ if (id != null)
+ setKey(lastId, id)
+ else
+ removeKey(lastId)
+
+ intent.setPackage(packageString)
+ callback.invoke(intent)
+ launcher?.launch(intent)
+ }
+
+ open fun getPosition(intent: Intent?): Long {
+ return defaultTime
+ }
+
+ open fun getDuration(intent: Intent?): Long {
+ return defaultTime
+ }
+}
+
+val VLC = object : ResultResume(
+ VLC_PACKAGE,
+ // Android 13 intent restrictions fucks up specifically launching the VLC player
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
+ "org.videolan.vlc.player.result"
+ } else {
+ Intent.ACTION_VIEW
+ },
+ "extra_position",
+ "extra_duration",
+) {
+ override fun getPosition(intent: Intent?): Long {
+ return intent?.getLongExtra(this.position, defaultTime) ?: defaultTime
+ }
+
+ override fun getDuration(intent: Intent?): Long {
+ return intent?.getLongExtra(this.duration, defaultTime) ?: defaultTime
+ }
+}
+
+val MPV = object : ResultResume(
+ MPV_PACKAGE,
+ //"is.xyz.mpv.MPVActivity.result", // resume not working :pensive:
+ position = "position",
+ duration = "duration",
+) {
+ override fun getPosition(intent: Intent?): Long {
+ return intent?.getIntExtra(this.position, defaultTime.toInt())?.toLong() ?: defaultTime
+ }
+
+ override fun getDuration(intent: Intent?): Long {
+ return intent?.getIntExtra(this.duration, defaultTime.toInt())?.toLong() ?: defaultTime
+ }
+}
+
+val WEB_VIDEO = ResultResume(WEB_VIDEO_CAST_PACKAGE)
+
+val resumeApps = arrayOf(
+ VLC, MPV, WEB_VIDEO
+)
+
+// Short name for requests client to make it nicer to use
+
+var app = Requests(responseParser = object : ResponseParser {
+ val mapper: ObjectMapper = jacksonObjectMapper().configure(
+ DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
+ false
+ )
+
+ override fun parse(text: String, kClass: KClass): T {
+ return mapper.readValue(text, kClass.java)
+ }
+
+ override fun parseSafe(text: String, kClass: KClass): T? {
+ return try {
+ mapper.readValue(text, kClass.java)
+ } catch (e: Exception) {
+ null
+ }
+ }
+
+ override fun writeValueAsString(obj: Any): String {
+ return mapper.writeValueAsString(obj)
+ }
+}).apply {
+ defaultHeaders = mapOf("user-agent" to USER_AGENT)
+}
+
+class MainActivity : AppCompatActivity(), ColorPickerDialogListener,
+ BiometricAuthenticator.BiometricAuthCallback {
companion object {
- const val VLC_PACKAGE = "org.videolan.vlc"
- const val MPV_PACKAGE = "is.xyz.mpv"
- const val WEB_VIDEO_CAST_PACKAGE = "com.instantbits.cast.webvideo"
-
- val VLC_COMPONENT = ComponentName(VLC_PACKAGE, "$VLC_PACKAGE.gui.video.VideoPlayerActivity")
- val MPV_COMPONENT = ComponentName(MPV_PACKAGE, "$MPV_PACKAGE.MPVActivity")
-
- //TODO REFACTOR AF
- open class ResultResume(
- val packageString: String,
- val action: String = Intent.ACTION_VIEW,
- val position: String? = null,
- val duration: String? = null,
- var launcher: ActivityResultLauncher? = null,
- ) {
- val defaultTime = -1L
-
- val lastId get() = "${packageString}_last_open_id"
- suspend fun launch(id: Int?, callback: suspend Intent.() -> Unit) {
- val intent = Intent(action)
-
- if (id != null)
- setKey(lastId, id)
- else
- removeKey(lastId)
-
- intent.setPackage(packageString)
- callback.invoke(intent)
- launcher?.launch(intent)
- }
-
- open fun getPosition(intent: Intent?): Long {
- return defaultTime
- }
-
- open fun getDuration(intent: Intent?): Long {
- return defaultTime
- }
- }
-
- val VLC = object : ResultResume(
- VLC_PACKAGE,
- // Android 13 intent restrictions fucks up specifically launching the VLC player
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
- "org.videolan.vlc.player.result"
- } else {
- Intent.ACTION_VIEW
- },
- "extra_position",
- "extra_duration",
- ) {
- override fun getPosition(intent: Intent?): Long {
- return intent?.getLongExtra(this.position, defaultTime) ?: defaultTime
- }
-
- override fun getDuration(intent: Intent?): Long {
- return intent?.getLongExtra(this.duration, defaultTime) ?: defaultTime
- }
- }
-
- val MPV = object : ResultResume(
- MPV_PACKAGE,
- //"is.xyz.mpv.MPVActivity.result", // resume not working :pensive:
- position = "position",
- duration = "duration",
- ) {
- override fun getPosition(intent: Intent?): Long {
- return intent?.getIntExtra(this.position, defaultTime.toInt())?.toLong()
- ?: defaultTime
- }
-
- override fun getDuration(intent: Intent?): Long {
- return intent?.getIntExtra(this.duration, defaultTime.toInt())?.toLong()
- ?: defaultTime
- }
- }
-
- val WEB_VIDEO = ResultResume(WEB_VIDEO_CAST_PACKAGE)
-
- val resumeApps = arrayOf(
- VLC, MPV, WEB_VIDEO
- )
-
-
const val TAG = "MAINACT"
const val ANIMATED_OUTLINE: Boolean = false
var lastError: String? = null
@@ -351,7 +371,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
println("Repository url: $realUrl")
loadRepository(realUrl)
return true
- } else if (str.contains(APP_STRING)) {
+ } else if (str.contains(appString)) {
for (api in OAuth2Apis) {
if (str.contains("/${api.redirectUrl}")) {
ioSafe {
@@ -381,15 +401,15 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
}
// This specific intent is used for the gradle deployWithAdb
// https://github.com/recloudstream/gradle/blob/master/src/main/kotlin/com/lagradost/cloudstream3/gradle/tasks/DeployWithAdbTask.kt#L46
- if (str == "$APP_STRING:") {
+ if (str == "$appString:") {
PluginManager.hotReloadAllLocalPlugins(activity)
}
- } else if (safeURI(str)?.scheme == APP_STRING_REPO) {
- val url = str.replaceFirst(APP_STRING_REPO, "https")
+ } else if (safeURI(str)?.scheme == appStringRepo) {
+ val url = str.replaceFirst(appStringRepo, "https")
loadRepository(url)
return true
- } else if (safeURI(str)?.scheme == APP_STRING_SEARCH) {
- val query = str.substringAfter("$APP_STRING_SEARCH://")
+ } else if (safeURI(str)?.scheme == appStringSearch) {
+ val query = str.substringAfter("$appStringSearch://")
nextSearchQuery =
try {
URLDecoder.decode(query, "UTF-8")
@@ -403,7 +423,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
R.id.navigation_search
activity?.findViewById(R.id.nav_rail_view)?.selectedItemId =
R.id.navigation_search
- } else if (safeURI(str)?.scheme == APP_STRING_PLAYER) {
+ } else if (safeURI(str)?.scheme == appStringPlayer) {
val uri = Uri.parse(str)
val name = uri.getQueryParameter("name")
val url = URLDecoder.decode(uri.authority, "UTF-8")
@@ -417,9 +437,9 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
)
)
)
- } else if (safeURI(str)?.scheme == APP_STRING_RESUME_WATCHING) {
+ } else if (safeURI(str)?.scheme == appStringResumeWatching) {
val id =
- str.substringAfter("$APP_STRING_RESUME_WATCHING://").toIntOrNull()
+ str.substringAfter("$appStringResumeWatching://").toIntOrNull()
?: return false
ioSafe {
val resumeWatchingCard =
@@ -473,7 +493,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
) DubStatus.Dubbed else DubStatus.Subbed, null
)
} else {
- viewModel.loadSmall(result)
+ viewModel.loadSmall(this, result)
}
}
@@ -488,7 +508,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
updateLocale() // android fucks me by chaining lang when rotating the phone
- updateTheme(this) // Update if system theme
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
@@ -573,44 +592,23 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
false
}
}
-
binding?.apply {
- navRailView.isVisible = isNavVisible && landscape
navView.isVisible = isNavVisible && !landscape
+ navRailView.isVisible = isNavVisible && landscape
- /**
- * We need to make sure if we return to a sub-fragment,
- * the correct navigation item is selected so that it does not
- * highlight the wrong one in UI.
- */
- when (destination.id) {
- in listOf(R.id.navigation_downloads, R.id.navigation_download_child) -> {
- navRailView.menu.findItem(R.id.navigation_downloads).isChecked = true
- navView.menu.findItem(R.id.navigation_downloads).isChecked = true
- }
- in listOf(
- R.id.navigation_settings,
- R.id.navigation_subtitles,
- R.id.navigation_chrome_subtitles,
- R.id.navigation_settings_player,
- R.id.navigation_settings_updates,
- R.id.navigation_settings_ui,
- R.id.navigation_settings_account,
- R.id.navigation_settings_providers,
- R.id.navigation_settings_general,
- R.id.navigation_settings_extensions,
- R.id.navigation_settings_plugins,
- R.id.navigation_test_providers
- ) -> {
- navRailView.menu.findItem(R.id.navigation_settings).isChecked = true
- navView.menu.findItem(R.id.navigation_settings).isChecked = true
- }
- }
+ // Hide library on TV since it is not supported yet :(
+ //val isTrueTv = isTrueTvSettings()
+ //navView.menu.findItem(R.id.navigation_library)?.isVisible = !isTrueTv
+ //navRailView.menu.findItem(R.id.navigation_library)?.isVisible = !isTrueTv
+
+ // Hide downloads on TV
+ //navView.menu.findItem(R.id.navigation_downloads)?.isVisible = !isTrueTv
+ //navRailView.menu.findItem(R.id.navigation_downloads)?.isVisible = !isTrueTv
}
}
//private var mCastSession: CastSession? = null
- var mSessionManager: SessionManager? = null
+ lateinit var mSessionManager: SessionManager
private val mSessionManagerListener: SessionManagerListener by lazy { SessionManagerListenerImpl() }
private inner class SessionManagerListenerImpl : SessionManagerListener {
@@ -650,7 +648,8 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
setActivityInstance(this)
try {
if (isCastApiAvailable()) {
- mSessionManager?.addSessionManagerListener(mSessionManagerListener)
+ //mCastSession = mSessionManager.currentCastSession
+ mSessionManager.addSessionManagerListener(mSessionManagerListener)
}
} catch (e: Exception) {
logError(e)
@@ -666,7 +665,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
}
try {
if (isCastApiAvailable()) {
- mSessionManager?.removeSessionManagerListener(mSessionManagerListener)
+ mSessionManager.removeSessionManagerListener(mSessionManagerListener)
//mCastSession = null
}
} catch (e: Exception) {
@@ -674,7 +673,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
}
}
- override fun dispatchKeyEvent(event: KeyEvent): Boolean {
+ override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
val response = CommonActivity.dispatchKeyEvent(this, event)
if (response != null)
return response
@@ -770,7 +769,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
list.forEach { custom ->
allProviders.firstOrNull { it.javaClass.simpleName == custom.parentJavaClass }
?.let {
- allProviders.add(it.javaClass.getDeclaredConstructor().newInstance().apply {
+ allProviders.add(it.javaClass.newInstance().apply {
name = custom.name
lang = custom.lang
mainUrl = custom.url.trimEnd('/')
@@ -793,14 +792,14 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
lateinit var viewModel: ResultViewModel2
lateinit var syncViewModel: SyncViewModel
- private var libraryViewModel: LibraryViewModel? = null
/** kinda dirty, however it signals that we should use the watch status as sync or not*/
var isLocalList: Boolean = false
override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {
-
- viewModel = ViewModelProvider(this)[ResultViewModel2::class.java]
- syncViewModel = ViewModelProvider(this)[SyncViewModel::class.java]
+ viewModel =
+ ViewModelProvider(this)[ResultViewModel2::class.java]
+ syncViewModel =
+ ViewModelProvider(this)[SyncViewModel::class.java]
return super.onCreateView(name, context, attrs)
}
@@ -1151,7 +1150,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
super.onCreate(savedInstanceState)
try {
if (isCastApiAvailable()) {
- CastContext.getSharedInstance(this) {it.run()}.addOnSuccessListener { mSessionManager = it.sessionManager }
+ mSessionManager = CastContext.getSharedInstance(this).sessionManager
}
} catch (t: Throwable) {
logError(t)
@@ -1232,17 +1231,18 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
changeStatusBarState(isLayout(EMULATOR))
/** Biometric stuff for users without accounts **/
+ val authEnabled = settingsManager.getBoolean(getString(R.string.biometric_key), false)
val noAccounts = settingsManager.getBoolean(
getString(R.string.skip_startup_account_select_key),
false
) || accounts.count() <= 1
- if (isLayout(PHONE) && isAuthEnabled(this) && noAccounts) {
+ if (isLayout(PHONE) && authEnabled && noAccounts) {
if (deviceHasPasswordPinLock(this)) {
startBiometricAuthentication(this, R.string.biometric_authentication_title, false)
- promptInfo?.let { prompt ->
- biometricPrompt?.authenticate(prompt)
+ BiometricAuthenticator.promptInfo?.let { promt ->
+ BiometricAuthenticator.biometricPrompt?.authenticate(promt)
}
// hide background while authenticating, Sorry moms & dads 🙏
@@ -1257,12 +1257,17 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
this.setKey(getString(R.string.jsdelivr_proxy_key), false)
} else {
this.setKey(getString(R.string.jsdelivr_proxy_key), true)
- showSnackbar(
- this@MainActivity,
- R.string.jsdelivr_enabled,
- Snackbar.LENGTH_LONG,
- R.string.revert
- ) { setKey(getString(R.string.jsdelivr_proxy_key), false) }
+ val parentView: View = findViewById(android.R.id.content)
+ Snackbar.make(parentView, R.string.jsdelivr_enabled, Snackbar.LENGTH_LONG)
+ .let { snackbar ->
+ snackbar.setAction(R.string.revert) {
+ setKey(getString(R.string.jsdelivr_proxy_key), false)
+ }
+ snackbar.setBackgroundTint(colorFromAttribute(R.attr.primaryGrayBackground))
+ snackbar.setTextColor(colorFromAttribute(R.attr.textColor))
+ snackbar.setActionTextColor(colorFromAttribute(R.attr.colorPrimary))
+ snackbar.show()
+ }
}
}
}
@@ -1395,7 +1400,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
}
}
- observe(viewModel.watchStatus, ::setWatchStatus)
+ observe(viewModel.watchStatus,::setWatchStatus)
observe(syncViewModel.userData, ::setUserData)
observeNullable(viewModel.subscribeStatus, ::setSubscribeStatus)
@@ -1433,7 +1438,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
resultviewPreviewMetaDuration.setText(d.durationText)
resultviewPreviewMetaRating.setText(d.ratingText)
- resultviewPreviewDescription.setTextHtml(d.plotText)
+ resultviewPreviewDescription.setText(d.plotText)
resultviewPreviewPoster.setImage(
d.posterImage ?: d.posterBackgroundImage
)
@@ -1448,13 +1453,13 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
val value = viewModel.watchStatus.value ?: WatchType.NONE
this@MainActivity.showBottomDialog(
- WatchType.entries.map { getString(it.stringRes) }.toList(),
+ WatchType.values().map { getString(it.stringRes) }.toList(),
value.ordinal,
this@MainActivity.getString(R.string.action_add_to_bookmarks),
showApply = false,
{}) {
viewModel.updateWatchStatus(
- WatchType.entries[it],
+ WatchType.values()[it],
this@MainActivity
)
}
@@ -1464,12 +1469,12 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
?: SyncWatchType.NONE
this@MainActivity.showBottomDialog(
- SyncWatchType.entries.map { getString(it.stringRes) }.toList(),
+ SyncWatchType.values().map { getString(it.stringRes) }.toList(),
value.ordinal,
this@MainActivity.getString(R.string.action_add_to_bookmarks),
showApply = false,
{}) {
- syncViewModel.setStatus(SyncWatchType.entries[it].internalId)
+ syncViewModel.setStatus(SyncWatchType.values()[it].internalId)
syncViewModel.publishUserData()
}
}
@@ -1550,26 +1555,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
logError(e)
}
}
-
- // we need to run this after we init all apis, otherwise currentSyncApi will fuck itself
- this@MainActivity.runOnUiThread {
- // Change library icon with logo of current api in sync
- libraryViewModel = ViewModelProvider(this@MainActivity)[LibraryViewModel::class.java]
- libraryViewModel?.currentApiName?.observe(this@MainActivity) {
- val syncAPI = libraryViewModel?.currentSyncApi
- Log.i("SYNC_API", "${syncAPI?.name}, ${syncAPI?.idPrefix}")
- val icon = if (syncAPI?.idPrefix == localListApi.idPrefix) {
- R.drawable.library_icon
- } else {
- syncAPI?.icon ?: R.drawable.library_icon
- }
-
- binding?.apply {
- navRailView.menu.findItem(R.id.navigation_library)?.setIcon(icon)
- navView.menu.findItem(R.id.navigation_library)?.setIcon(icon)
- }
- }
- }
}
SearchResultBuilder.updateCache(this)
@@ -1601,12 +1586,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
if (isLayout(TV or EMULATOR)) {
if (navDestination.matchDestination(R.id.navigation_home)) {
- attachBackPressedCallback {
- showConfirmExitDialog()
- window?.navigationBarColor =
- colorFromAttribute(R.attr.primaryGrayBackground)
- updateLocale()
- }
+ attachBackPressedCallback()
} else detachBackPressedCallback()
}
}
@@ -1774,8 +1754,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
runAutoUpdate()
}
- FcastManager().init(this, false)
-
APIRepository.dubStatusActive = getApiDubstatusSettings()
try {
@@ -1847,8 +1825,26 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
binding?.navHostFragment?.isInvisible = false
}
- override fun onAuthenticationError() {
- finish()
+ private var backPressedCallback: OnBackPressedCallback? = null
+
+ private fun attachBackPressedCallback() {
+ if (backPressedCallback == null) {
+ backPressedCallback = object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ showConfirmExitDialog()
+ window?.navigationBarColor =
+ colorFromAttribute(R.attr.primaryGrayBackground)
+ updateLocale()
+ }
+ }
+ }
+
+ backPressedCallback?.isEnabled = true
+ onBackPressedDispatcher.addCallback(this, backPressedCallback ?: return)
+ }
+
+ private fun detachBackPressedCallback() {
+ backPressedCallback?.isEnabled = false
}
suspend fun checkGithubConnectivity(): Boolean {
@@ -1861,4 +1857,4 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
false
}
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/NativeCrashHandler.kt b/app/src/main/java/com/lagradost/cloudstream3/NativeCrashHandler.kt
new file mode 100644
index 00000000..7be90440
--- /dev/null
+++ b/app/src/main/java/com/lagradost/cloudstream3/NativeCrashHandler.kt
@@ -0,0 +1,53 @@
+package com.lagradost.cloudstream3
+
+import com.lagradost.cloudstream3.MainActivity.Companion.lastError
+import com.lagradost.cloudstream3.mvvm.logError
+import com.lagradost.cloudstream3.plugins.PluginManager.checkSafeModeFile
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+object NativeCrashHandler {
+ // external fun triggerNativeCrash()
+ /*private external fun initNativeCrashHandler()
+ private external fun getSignalStatus(): Int
+
+ private fun initSignalPolling() = CoroutineScope(Dispatchers.IO).launch {
+
+ //launch {
+ // delay(10000)
+ // triggerNativeCrash()
+ //}
+
+ while (true) {
+ delay(10_000)
+ val signal = getSignalStatus()
+ // Signal is initialized to zero
+ if (signal == 0) continue
+
+ // Do not crash in safe mode!
+ if (lastError != null) continue
+ if (checkSafeModeFile()) continue
+
+ AcraApplication.exceptionHandler?.uncaughtException(
+ Thread.currentThread(),
+ RuntimeException("Native crash with code: $signal. Try uninstalling extensions.\n")
+ )
+ }
+ }
+
+ fun initCrashHandler() {
+ try {
+ System.loadLibrary("native-lib")
+ initNativeCrashHandler()
+ } catch (t: Throwable) {
+ // Make debug crash.
+ if (BuildConfig.DEBUG) throw t
+ logError(t)
+ return
+ }
+
+ initSignalPolling()
+ }*/
+}
\ No newline at end of file
diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/ParCollections.kt b/app/src/main/java/com/lagradost/cloudstream3/ParCollections.kt
similarity index 100%
rename from library/src/commonMain/kotlin/com/lagradost/cloudstream3/ParCollections.kt
rename to app/src/main/java/com/lagradost/cloudstream3/ParCollections.kt
diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/AStreamHub.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/AStreamHub.kt
similarity index 97%
rename from library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/AStreamHub.kt
rename to app/src/main/java/com/lagradost/cloudstream3/extractors/AStreamHub.kt
index 23f8dcf4..b0051ba7 100644
--- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/AStreamHub.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/AStreamHub.kt
@@ -1,6 +1,6 @@
package com.lagradost.cloudstream3.extractors
-import com.lagradost.api.Log
+import android.util.Log
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Acefile.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Acefile.kt
similarity index 100%
rename from library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Acefile.kt
rename to app/src/main/java/com/lagradost/cloudstream3/extractors/Acefile.kt
diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/AsianLoad.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/AsianLoad.kt
similarity index 100%
rename from library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/AsianLoad.kt
rename to app/src/main/java/com/lagradost/cloudstream3/extractors/AsianLoad.kt
diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Blogger.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Blogger.kt
similarity index 100%
rename from library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Blogger.kt
rename to app/src/main/java/com/lagradost/cloudstream3/extractors/Blogger.kt
diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/BullStream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/BullStream.kt
similarity index 100%
rename from library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/BullStream.kt
rename to app/src/main/java/com/lagradost/cloudstream3/extractors/BullStream.kt
diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/ByteShare.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/ByteShare.kt
similarity index 100%
rename from library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/ByteShare.kt
rename to app/src/main/java/com/lagradost/cloudstream3/extractors/ByteShare.kt
diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Cda.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Cda.kt
similarity index 100%
rename from library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Cda.kt
rename to app/src/main/java/com/lagradost/cloudstream3/extractors/Cda.kt
diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Chillx.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Chillx.kt
similarity index 68%
rename from library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Chillx.kt
rename to app/src/main/java/com/lagradost/cloudstream3/extractors/Chillx.kt
index dd22efb2..f03a5525 100644
--- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Chillx.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Chillx.kt
@@ -2,11 +2,12 @@ package com.lagradost.cloudstream3.extractors
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.*
+import com.lagradost.cloudstream3.extractors.helper.*
import com.lagradost.cloudstream3.extractors.helper.AesHelper.cryptoAESHandler
+import com.lagradost.cloudstream3.utils.AppUtils
import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.M3u8Helper
-import kotlin.run
class Moviesapi : Chillx() {
override val name = "Moviesapi"
@@ -27,44 +28,30 @@ open class Chillx : ExtractorApi() {
override val name = "Chillx"
override val mainUrl = "https://chillx.top"
override val requiresReferer = true
+ private var key: String? = null
- companion object {
- private val keySource = "https://rowdy-avocado.github.io/multi-keys/"
-
- private var key: String? = null
-
- private suspend fun fetchKey(): String {
- return key
- ?: run {
- val res =
- app.get(keySource).parsedSafe()
- ?: throw ErrorLoadingException("Unable to get keys")
- key = res.keys.get(0)
- res.keys.get(0)
- }
- }
-
- private data class KeysData(@JsonProperty("chillx") val keys: List)
- }
-
- @Suppress("NAME_SHADOWING")
override suspend fun getUrl(
url: String,
referer: String?,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
) {
- val master = Regex("""JScript[\w+]?\s*=\s*'([^']+)""").find(
+ val master = Regex("\\s*=\\s*'([^']+)").find(
app.get(
url,
- referer = url,
+ referer = referer ?: "",
+ headers = mapOf(
+ "Accept" to "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
+ "Accept-Language" to "en-US,en;q=0.5",
+ )
).text
)?.groupValues?.get(1)
- val key = fetchKey()
- val decrypt = cryptoAESHandler(master ?: "", key.toByteArray(), false)?.replace("\\", "") ?: throw ErrorLoadingException("failed to decrypt")
+ val decrypt = cryptoAESHandler(master ?: return, getKey().toByteArray(), false)?.replace("\\", "") ?: throw ErrorLoadingException("failed to decrypt")
+
val source = Regex(""""?file"?:\s*"([^"]+)""").find(decrypt)?.groupValues?.get(1)
+
val subtitles = Regex("""subtitle"?:\s*"([^"]+)""").find(decrypt)?.groupValues?.get(1)
- val subtitlePattern = """\[(.*?)](https?://[^\s,]+)""".toRegex()
+ val subtitlePattern = """\[(.*?)\](https?://[^\s,]+)""".toRegex()
val matches = subtitlePattern.findAll(subtitles ?: "")
val languageUrlPairs = matches.map { matchResult ->
val (language, url) = matchResult.destructured
@@ -96,11 +83,23 @@ open class Chillx : ExtractorApi() {
headers = headers
).forEach(callback)
}
-
+
private fun decodeUnicodeEscape(input: String): String {
val regex = Regex("u([0-9a-fA-F]{4})")
return regex.replace(input) {
it.groupValues[1].toInt(16).toChar().toString()
}
}
+
+ suspend fun getKey() = key ?: fetchKey().also { key = it }
+
+ private suspend fun fetchKey(): String {
+ return app.get("https://raw.githubusercontent.com/Sofie99/Resources/main/chillix_key.json").parsed()
+ }
+
+ data class Tracks(
+ @JsonProperty("file") val file: String? = null,
+ @JsonProperty("label") val label: String? = null,
+ @JsonProperty("kind") val kind: String? = null,
+ )
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/ContentXExtractor.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/ContentXExtractor.kt
new file mode 100644
index 00000000..b7f84af1
--- /dev/null
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/ContentXExtractor.kt
@@ -0,0 +1,69 @@
+// ! Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
+
+package com.lagradost.cloudstream3.extractors
+
+import android.util.Log
+import com.lagradost.cloudstream3.*
+import com.lagradost.cloudstream3.utils.*
+
+open class ContentX : ExtractorApi() {
+ override val name = "ContentX"
+ override val mainUrl = "https://contentx.me"
+ override val requiresReferer = true
+
+ override suspend fun getUrl(url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit) {
+ val ext_ref = referer ?: ""
+ Log.d("Kekik_${this.name}", "url » ${url}")
+
+ val i_source = app.get(url, referer=ext_ref).text
+ val i_extract = Regex("""window\.openPlayer\('([^']+)'""").find(i_source)!!.groups[1]?.value ?: throw ErrorLoadingException("i_extract is null")
+
+ val sub_urls = mutableSetOf()
+ Regex("""\"file\":\"([^\"]+)\",\"label\":\"([^\"]+)\"""").findAll(i_source).forEach {
+ val (sub_url, sub_lang) = it.destructured
+
+ if (sub_url in sub_urls) { return@forEach }
+ sub_urls.add(sub_url)
+
+ subtitleCallback.invoke(
+ SubtitleFile(
+ lang = sub_lang.replace("\\u0131", "ı").replace("\\u0130", "İ").replace("\\u00fc", "ü").replace("\\u00e7", "ç"),
+ url = fixUrl(sub_url.replace("\\", ""))
+ )
+ )
+ }
+
+ val vid_source = app.get("${mainUrl}/source2.php?v=${i_extract}", referer=ext_ref).text
+ val vid_extract = Regex("""file\":\"([^\"]+)""").find(vid_source)!!.groups[1]?.value ?: throw ErrorLoadingException("vid_extract is null")
+ val m3u_link = vid_extract.replace("\\", "")
+
+ callback.invoke(
+ ExtractorLink(
+ source = this.name,
+ name = this.name,
+ url = m3u_link,
+ referer = url,
+ quality = Qualities.Unknown.value,
+ isM3u8 = true
+ )
+ )
+
+ val i_dublaj = Regex(""",\"([^']+)\",\"Türkçe""").find(i_source)!!.groups[1]?.value
+ if (i_dublaj != null) {
+ val dublaj_source = app.get("${mainUrl}/source2.php?v=${i_dublaj}", referer=ext_ref).text
+ val dublaj_extract = Regex("""file\":\"([^\"]+)""").find(dublaj_source)!!.groups[1]?.value ?: throw ErrorLoadingException("dublaj_extract is null")
+ val dublaj_link = dublaj_extract.replace("\\", "")
+
+ callback.invoke(
+ ExtractorLink(
+ source = "${this.name} Türkçe Dublaj",
+ name = "${this.name} Türkçe Dublaj",
+ url = dublaj_link,
+ referer = url,
+ quality = Qualities.Unknown.value,
+ isM3u8 = true
+ )
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Dailymotion.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Dailymotion.kt
similarity index 84%
rename from library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Dailymotion.kt
rename to app/src/main/java/com/lagradost/cloudstream3/extractors/Dailymotion.kt
index 2343a92e..0df93dc5 100644
--- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Dailymotion.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Dailymotion.kt
@@ -9,16 +9,10 @@ import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8
import java.net.URL
-class Geodailymotion : Dailymotion() {
- override val name = "GeoDailymotion"
- override val mainUrl = "https://geo.dailymotion.com"
-}
-
open class Dailymotion : ExtractorApi() {
override val mainUrl = "https://www.dailymotion.com"
override val name = "Dailymotion"
override val requiresReferer = false
- private val baseUrl = "https://www.dailymotion.com"
@Suppress("RegExpSimplifiable")
private val videoIdRegex = "^[kx][a-zA-Z0-9]+\$".toRegex()
@@ -40,7 +34,7 @@ open class Dailymotion : ExtractorApi() {
val dmV1st = config.dmInternalData.v1st
val dmTs = config.dmInternalData.ts
val embedder = config.context.embedder
- val metaDataUrl = "$baseUrl/player/metadata/video/$id?embedder=$embedder&locale=en-US&dmV1st=$dmV1st&dmTs=$dmTs&is_native_app=0"
+ val metaDataUrl = "$mainUrl/player/metadata/video/$id?embedder=$embedder&locale=en-US&dmV1st=$dmV1st&dmTs=$dmTs&is_native_app=0"
val metaData = app.get(metaDataUrl, referer = embedUrl, cookies = req.cookies)
.parsedSafe() ?: return
metaData.qualities.forEach { (_, video) ->
@@ -51,19 +45,16 @@ open class Dailymotion : ExtractorApi() {
}
private fun getEmbedUrl(url: String): String? {
- if (url.contains("/embed/") || url.contains("/video/")) {
- return url
+ if (url.contains("/embed/")) {
+ return url
+ }
+ val vid = getVideoId(url) ?: return null
+ return "$mainUrl/embed/video/$vid"
}
- if (url.contains("geo.dailymotion.com")) {
- val videoId = url.substringAfter("video=")
- return "$baseUrl/embed/video/$videoId"
- }
- return null
- }
private fun getVideoId(url: String): String? {
val path = URL(url).path
- val id = path.substringAfter("/video/")
+ val id = path.substringAfter("video/")
if (id.matches(videoIdRegex)) {
return id
}
diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/DoodExtractor.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/DoodExtractor.kt
similarity index 73%
rename from library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/DoodExtractor.kt
rename to app/src/main/java/com/lagradost/cloudstream3/extractors/DoodExtractor.kt
index 370dcaca..8dcfb859 100644
--- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/DoodExtractor.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/DoodExtractor.kt
@@ -7,18 +7,6 @@ import com.lagradost.cloudstream3.utils.Qualities
import com.lagradost.cloudstream3.utils.getQualityFromName
import kotlinx.coroutines.delay
-class D0000d : DoodLaExtractor() {
- override var mainUrl = "https://d0000d.com"
-}
-
-class D000dCom : DoodLaExtractor() {
- override var mainUrl = "https://d000d.com"
-}
-
-class DoodstreamCom : DoodLaExtractor() {
- override var mainUrl = "https://doodstream.com"
-}
-
class Dooood : DoodLaExtractor() {
override var mainUrl = "https://dooood.com"
}
@@ -68,10 +56,9 @@ open class DoodLaExtractor : ExtractorApi() {
}
override suspend fun getUrl(url: String, referer: String?): List? {
- val newUrl= url.replace(mainUrl, "https://d0000d.com")
- val response0 = app.get(newUrl).text // html of DoodStream page to look for /pass_md5/...
- val md5 ="https://d0000d.com"+(Regex("/pass_md5/[^']*").find(response0)?.value ?: return null) // get https://dood.ws/pass_md5/...
- val trueUrl = app.get(md5, referer = newUrl).text + "zUEJeL3mUN?token=" + md5.substringAfterLast("/") //direct link to extract (zUEJeL3mUN is random)
+ val response0 = app.get(url).text // html of DoodStream page to look for /pass_md5/...
+ val md5 =mainUrl+(Regex("/pass_md5/[^']*").find(response0)?.value ?: return null) // get https://dood.ws/pass_md5/...
+ val trueUrl = app.get(md5, referer = url).text + "zUEJeL3mUN?token=" + md5.substringAfterLast("/") //direct link to extract (zUEJeL3mUN is random)
val quality = Regex("\\d{3,4}p").find(response0.substringAfter("").substringBefore(""))?.groupValues?.get(0)
return listOf(
ExtractorLink(
diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Embedgram.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Embedgram.kt
similarity index 100%
rename from library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Embedgram.kt
rename to app/src/main/java/com/lagradost/cloudstream3/extractors/Embedgram.kt
diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/EmturbovidExtractor.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/EmturbovidExtractor.kt
similarity index 100%
rename from library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/EmturbovidExtractor.kt
rename to app/src/main/java/com/lagradost/cloudstream3/extractors/EmturbovidExtractor.kt
diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Evolaod.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Evolaod.kt
similarity index 100%
rename from library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Evolaod.kt
rename to app/src/main/java/com/lagradost/cloudstream3/extractors/Evolaod.kt
diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Fastream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Fastream.kt
similarity index 100%
rename from library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Fastream.kt
rename to app/src/main/java/com/lagradost/cloudstream3/extractors/Fastream.kt
diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Filesim.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Filesim.kt
similarity index 100%
rename from library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Filesim.kt
rename to app/src/main/java/com/lagradost/cloudstream3/extractors/Filesim.kt
diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/GMPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/GMPlayer.kt
similarity index 100%
rename from library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/GMPlayer.kt
rename to app/src/main/java/com/lagradost/cloudstream3/extractors/GMPlayer.kt
diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Gdriveplayer.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Gdriveplayer.kt
similarity index 100%
rename from library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Gdriveplayer.kt
rename to app/src/main/java/com/lagradost/cloudstream3/extractors/Gdriveplayer.kt
diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/GenericM3U8.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/GenericM3U8.kt
similarity index 100%
rename from library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/GenericM3U8.kt
rename to app/src/main/java/com/lagradost/cloudstream3/extractors/GenericM3U8.kt
diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Gofile.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Gofile.kt
similarity index 100%
rename from library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Gofile.kt
rename to app/src/main/java/com/lagradost/cloudstream3/extractors/Gofile.kt
diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/GuardareStream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/GuardareStream.kt
similarity index 100%
rename from library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/GuardareStream.kt
rename to app/src/main/java/com/lagradost/cloudstream3/extractors/GuardareStream.kt
diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/HDMomPlayerExtractor.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/HDMomPlayerExtractor.kt
similarity index 76%
rename from library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/HDMomPlayerExtractor.kt
rename to app/src/main/java/com/lagradost/cloudstream3/extractors/HDMomPlayerExtractor.kt
index 1152cb4b..03586386 100644
--- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/HDMomPlayerExtractor.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/HDMomPlayerExtractor.kt
@@ -2,7 +2,7 @@
package com.lagradost.cloudstream3.extractors
-import com.lagradost.api.Log
+import android.util.Log
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.extractors.helper.AesHelper
@@ -16,23 +16,24 @@ open class HDMomPlayer : ExtractorApi() {
override val requiresReferer = true
override suspend fun getUrl(url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit) {
- val m3uLink:String?
- val extRef = referer ?: ""
- val iSource = app.get(url, referer=extRef).text
+ val m3u_link:String?
+ val ext_ref = referer ?: ""
+ val i_source = app.get(url, referer=ext_ref).text
- val bePlayer = Regex("""bePlayer\('([^']+)',\s*'(\{[^\}]+\})'\);""").find(iSource)?.groupValues
+ val bePlayer = Regex("""bePlayer\('([^']+)',\s*'(\{[^\}]+\})'\);""").find(i_source)?.groupValues
if (bePlayer != null) {
val bePlayerPass = bePlayer.get(1)
val bePlayerData = bePlayer.get(2)
val encrypted = AesHelper.cryptoAESHandler(bePlayerData, bePlayerPass.toByteArray(), false)?.replace("\\", "") ?: throw ErrorLoadingException("failed to decrypt")
+ Log.d("Kekik_${this.name}", "encrypted » ${encrypted}")
- m3uLink = Regex("""video_location\":\"([^\"]+)""").find(encrypted)?.groupValues?.get(1)
+ m3u_link = Regex("""video_location\":\"([^\"]+)""").find(encrypted)?.groupValues?.get(1)
} else {
- m3uLink = Regex("""file:\"([^\"]+)""").find(iSource)?.groupValues?.get(1)
+ m3u_link = Regex("""file:\"([^\"]+)""").find(i_source)?.groupValues?.get(1)
- val trackStr = Regex("""tracks:\[([^\]]+)""").find(iSource)?.groupValues?.get(1)
- if (trackStr != null) {
- val tracks:List