Compare commits

..

1 commit

Author SHA1 Message Date
firelight
540b17094f
Revert "feat: make cloudstream compilation and builds fast! using gradle conf…"
This reverts commit 21b341e12f.
2024-03-08 02:07:14 +01:00
470 changed files with 7246 additions and 15966 deletions

View file

@ -80,13 +80,13 @@ body:
label: Acknowledgements label: Acknowledgements
description: Your issue will be closed if you haven't done these steps. description: Your issue will be closed if you haven't done these steps.
options: 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. - label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue.
required: true required: true
- label: I have written a short but informative title. - label: I have written a short but informative title.
required: true required: true
- label: I have updated the app to pre-release version **[Latest](https://github.com/recloudstream/cloudstream/releases)**. - label: I have updated the app to pre-release version **[Latest](https://github.com/recloudstream/cloudstream/releases)**.
required: true 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. - label: I will fill out all of the requested information in this form.
required: true required: true

View file

@ -2,7 +2,7 @@ blank_issues_enabled: false
contact_links: contact_links:
- name: Request a new provider or report bug with an existing provider - name: Request a new provider or report bug with an existing provider
url: https://github.com/recloudstream 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 - name: Discord
url: https://discord.gg/5Hus6fM url: https://discord.gg/5Hus6fM
about: Join our discord for faster support on smaller issues. about: Join our discord for faster support on smaller issues.

View file

@ -27,7 +27,9 @@ body:
label: Acknowledgements label: Acknowledgements
description: Your issue will be closed if you haven't done these steps. description: Your issue will be closed if you haven't done these steps.
options: 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. - label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open issue.
required: true 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

6
.github/locales.py vendored
View file

@ -1,7 +1,6 @@
import re import re
import glob import glob
import requests import requests
import os
import lxml.etree as ET # builtin library doesn't preserve comments 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: try:
tree = ET.parse(file) tree = ET.parse(file)
for child in tree.getroot(): for child in tree.getroot():
if not child.text:
continue
if child.text.startswith("\\@string/"): if child.text.startswith("\\@string/"):
print(f"[{file}] fixing {child.attrib['name']}") print(f"[{file}] fixing {child.attrib['name']}")
child.text = child.text.replace("\\@string/", "@string/") child.text = child.text.replace("\\@string/", "@string/")
with open(file, 'wb') as fp: with open(file, 'wb') as fp:
fp.write(b'<?xml version="1.0" encoding="utf-8"?>\n') fp.write(b'<?xml version="1.0" encoding="utf-8"?>\n')
tree.write(fp, encoding="utf-8", method="xml", pretty_print=True, xml_declaration=False) 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: except ET.ParseError as ex:
print(f"[{file}] {ex}") print(f"[{file}] {ex}")

7
.idea/gradle.xml generated
View file

@ -4,16 +4,17 @@
<component name="GradleSettings"> <component name="GradleSettings">
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>
<option name="delegatedBuild" value="true" />
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" /> <option name="gradleJvm" value="jbr-17" />
<option name="modules"> <option name="modules">
<set> <set>
<option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" /> <option value="$PROJECT_DIR$/app" />
<option value="$PROJECT_DIR$/library" />
</set> </set>
</option> </option>
<option name="resolveExternalAnnotations" value="false" />
</GradleProjectSettings> </GradleProjectSettings>
</option> </option>
</component> </component>

View file

@ -1,6 +1,5 @@
import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties
import org.jetbrains.dokka.gradle.DokkaTask import org.jetbrains.dokka.gradle.DokkaTask
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.archivesName
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.net.URL import java.net.URL
@ -42,8 +41,8 @@ android {
}*/ }*/
signingConfigs { signingConfigs {
if (prereleaseStoreFile != null) {
create("prerelease") { create("prerelease") {
if (prereleaseStoreFile != null) {
storeFile = file(prereleaseStoreFile) storeFile = file(prereleaseStoreFile)
storePassword = System.getenv("SIGNING_STORE_PASSWORD") storePassword = System.getenv("SIGNING_STORE_PASSWORD")
keyAlias = System.getenv("SIGNING_KEY_ALIAS") keyAlias = System.getenv("SIGNING_KEY_ALIAS")
@ -60,8 +59,8 @@ android {
minSdk = 21 minSdk = 21
targetSdk = 33 /* Android 14 is Fu*ked targetSdk = 33 /* Android 14 is Fu*ked
^ https://developer.android.com/about/versions/14/behavior-changes-14#safer-dynamic-code-loading*/ ^ https://developer.android.com/about/versions/14/behavior-changes-14#safer-dynamic-code-loading*/
versionCode = 64 versionCode = 63
versionName = "4.4.0" versionName = "4.3.1"
resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}") resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}")
resValue("string", "commit_hash", "git rev-parse --short HEAD".execute() ?: "") resValue("string", "commit_hash", "git rev-parse --short HEAD".execute() ?: "")
@ -71,9 +70,9 @@ android {
val localProperties = gradleLocalProperties(rootDir) val localProperties = gradleLocalProperties(rootDir)
buildConfigField( buildConfigField(
"long", "String",
"BUILD_DATE", "BUILDDATE",
"${System.currentTimeMillis()}" "new java.text.SimpleDateFormat(\"yyyy-MM-dd HH:mm\").format(new java.util.Date(" + System.currentTimeMillis() + "L));"
) )
buildConfigField( buildConfigField(
"String", "String",
@ -124,11 +123,7 @@ android {
resValue("bool", "is_prerelease", "true") resValue("bool", "is_prerelease", "true")
buildConfigField("boolean", "BETA", "true") buildConfigField("boolean", "BETA", "true")
applicationIdSuffix = ".prerelease" applicationIdSuffix = ".prerelease"
if (signingConfigs.names.contains("prerelease")) {
signingConfig = signingConfigs.getByName("prerelease") signingConfig = signingConfigs.getByName("prerelease")
} else {
logger.warn("No prerelease signing config!")
}
versionNameSuffix = "-PRE" versionNameSuffix = "-PRE"
versionCode = (System.currentTimeMillis() / 60000).toInt() versionCode = (System.currentTimeMillis() / 60000).toInt()
} }
@ -159,24 +154,24 @@ repositories {
dependencies { dependencies {
// Testing // Testing
testImplementation("junit:junit:4.13.2") testImplementation("junit:junit:4.13.2")
testImplementation("org.json:json:20240303") testImplementation("org.json:json:20231013")
androidTestImplementation("androidx.test:core") androidTestImplementation("androidx.test:core")
implementation("androidx.test.ext:junit-ktx:1.2.1") implementation("androidx.test.ext:junit-ktx:1.1.5")
androidTestImplementation("androidx.test.ext:junit:1.2.1") androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
// Android Core & Lifecycle // Android Core & Lifecycle
implementation("androidx.core:core-ktx:1.13.1") implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.appcompat:appcompat:1.7.0") implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.navigation:navigation-ui-ktx:2.7.7") implementation("androidx.navigation:navigation-ui-ktx:2.7.6")
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.8.3") implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.3") implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
implementation("androidx.navigation:navigation-fragment-ktx:2.7.7") implementation("androidx.navigation:navigation-fragment-ktx:2.7.6")
// Design & UI // Design & UI
implementation("jp.wasabeef:glide-transformations:4.3.0") implementation("jp.wasabeef:glide-transformations:4.3.0")
implementation("androidx.preference:preference-ktx:1.2.1") implementation("androidx.preference:preference-ktx:1.2.1")
implementation("com.google.android.material:material:1.12.0") implementation("com.google.android.material:material:1.10.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4") implementation("androidx.constraintlayout:constraintlayout:2.1.4")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
@ -186,9 +181,9 @@ dependencies {
implementation("com.github.bumptech.glide:okhttp3-integration:4.16.0") implementation("com.github.bumptech.glide:okhttp3-integration:4.16.0")
// For KSP -> Official Annotation Processors are Not Yet Supported for KSP // For KSP -> Official Annotation Processors are Not Yet Supported for KSP
ksp("dev.zacsweers.autoservice:auto-service-ksp:1.2.0") ksp("dev.zacsweers.autoservice:auto-service-ksp:1.1.0")
implementation("com.google.guava:guava:33.2.1-android") implementation("com.google.guava:guava:32.1.3-android")
implementation("dev.zacsweers.autoservice:auto-service-ksp:1.2.0") implementation("dev.zacsweers.autoservice:auto-service-ksp:1.1.0")
// Media 3 (ExoPlayer) // Media 3 (ExoPlayer)
implementation("androidx.media3:media3-ui:1.1.1") implementation("androidx.media3:media3-ui:1.1.1")
@ -204,9 +199,9 @@ dependencies {
// PlayBack // PlayBack
implementation("com.jaredrummler:colorpicker:1.1.0") // Subtitle Color Picker 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.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 */ ^ 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) // Crash Reports (AcraApplication.kt)
implementation("ch.acra:acra-core:5.11.3") implementation("ch.acra:acra-core:5.11.3")
@ -219,15 +214,16 @@ dependencies {
implementation("com.github.discord:OverlappingPanels:0.1.5") // Gestures 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("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 // 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("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("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("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 implementation("com.uwetrottmann.tmdb2:tmdb-java:2.10.0") // TMDB API v3 Wrapper Made with RetroFit
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs_nio:2.0.4") //nio flavor needed for NewPipeExtractor coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.1") /* JSON Parser 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 ^ Don't Bump Jackson above 2.13.1 , Crashes on Android TV's and FireSticks that have Min API
Level 25 or Less. */ Level 25 or Less. */
@ -236,46 +232,18 @@ dependencies {
implementation("androidx.work:work-runtime:2.9.0") implementation("androidx.work:work-runtime:2.9.0")
implementation("androidx.work:work-runtime-ktx:2.9.0") implementation("androidx.work:work-runtime-ktx:2.9.0")
implementation("com.github.Blatzar:NiceHttp:0.4.11") // HTTP Lib 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", Jar::class) {
})
}
tasks.register<Jar>("androidSourcesJar") {
archiveClassifier.set("sources") archiveClassifier.set("sources")
from(android.sourceSets.getByName("main").java.srcDirs) // Full Sources from(android.sourceSets.getByName("main").java.srcDirs) // Full Sources
} }
tasks.register<Copy>("copyJar") { // For GradLew Plugin
from( tasks.register("makeJar", Copy::class) {
"build/intermediates/compile_app_classes_jar/prereleaseDebug", from("build/intermediates/compile_app_classes_jar/prereleaseDebug")
"../library/build/libs" into("build")
) include("classes.jar")
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<Jar>("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"
} }
tasks.withType<KotlinCompile> { tasks.withType<KotlinCompile> {

View file

@ -154,7 +154,7 @@ class ExampleInstrumentedTest {
fun providerCorrectHomepage() { fun providerCorrectHomepage() {
runBlocking { runBlocking {
getAllProviders().toList().amap { api -> getAllProviders().toList().amap { api ->
TestingUtils.testHomepage(api, TestingUtils.Logger()) TestingUtils.testHomepage(api, ::println)
} }
} }
println("Done providerCorrectHomepage") println("Done providerCorrectHomepage")
@ -166,6 +166,7 @@ class ExampleInstrumentedTest {
TestingUtils.getDeferredProviderTests( TestingUtils.getDeferredProviderTests(
this, this,
getAllProviders(), getAllProviders(),
::println
) { _, _ -> } ) { _, _ -> }
} }
} }

View file

@ -97,7 +97,7 @@
--> -->
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden|navigation|uiMode" android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden|navigation"
android:exported="true" android:exported="true"
android:launchMode="singleTask" android:launchMode="singleTask"
android:resizeableActivity="true" android:resizeableActivity="true"

View file

@ -8,14 +8,11 @@ import android.content.Intent
import android.widget.Toast import android.widget.Toast
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import com.lagradost.api.setContext
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
import com.lagradost.cloudstream3.plugins.PluginManager import com.lagradost.cloudstream3.plugins.PluginManager
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.ui.settings.Globals.TV import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.utils.AppContextUtils.openBrowser
import com.lagradost.cloudstream3.utils.Coroutines.runOnMainThread import com.lagradost.cloudstream3.utils.Coroutines.runOnMainThread
import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStore.getKey
import com.lagradost.cloudstream3.utils.DataStore.getKeys import com.lagradost.cloudstream3.utils.DataStore.getKeys
@ -34,8 +31,8 @@ import org.acra.sender.ReportSenderFactory
import java.io.File import java.io.File
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.io.PrintStream import java.io.PrintStream
import java.lang.Exception
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import java.util.Locale
import kotlin.concurrent.thread import kotlin.concurrent.thread
import kotlin.system.exitProcess import kotlin.system.exitProcess
@ -82,8 +79,14 @@ class ExceptionHandler(val errorFile: File, val onError: (() -> Unit)) :
ACRA.errorReporter.handleException(error) ACRA.errorReporter.handleException(error)
try { try {
PrintStream(errorFile).use { ps -> PrintStream(errorFile).use { ps ->
ps.println("Currently loading extension: ${PluginManager.currentlyLoading ?: "none"}") ps.println(String.format("Currently loading extension: ${PluginManager.currentlyLoading ?: "none"}"))
ps.println("Fatal exception on thread ${thread.name} (${thread.id})") ps.println(
String.format(
"Fatal exception on thread %s (%d)",
thread.name,
thread.id
)
)
error.printStackTrace(ps) error.printStackTrace(ps)
} }
} catch (ignored: FileNotFoundException) { } catch (ignored: FileNotFoundException) {
@ -101,6 +104,7 @@ class AcraApplication : Application() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
//NativeCrashHandler.initCrashHandler()
ExceptionHandler(filesDir.resolve("last_error")) { ExceptionHandler(filesDir.resolve("last_error")) {
val intent = context!!.packageManager.getLaunchIntentForPackage(context!!.packageName) val intent = context!!.packageManager.getLaunchIntentForPackage(context!!.packageName)
startActivity(Intent.makeRestartActivityTask(intent!!.component)) startActivity(Intent.makeRestartActivityTask(intent!!.component))
@ -146,7 +150,6 @@ class AcraApplication : Application() {
get() = _context?.get() get() = _context?.get()
private set(value) { private set(value) {
_context = WeakReference(value) _context = WeakReference(value)
setContext(WeakReference(value))
} }
fun <T : Any> getKeyClass(path: String, valueType: Class<T>): T? { fun <T : Any> getKeyClass(path: String, valueType: Class<T>): T? {
@ -208,7 +211,7 @@ class AcraApplication : Application() {
fun openBrowser(url: String, activity: FragmentActivity?) { fun openBrowser(url: String, activity: FragmentActivity?) {
openBrowser( openBrowser(
url, url,
isLayout(TV or EMULATOR), isTvSettings(),
activity?.supportFragmentManager?.fragments?.lastOrNull() activity?.supportFragmentManager?.fragments?.lastOrNull()
) )
} }

View file

@ -5,16 +5,17 @@ import android.app.Activity
import android.app.PictureInPictureParams import android.app.PictureInPictureParams
import android.content.Context import android.content.Context
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.res.Configuration
import android.content.res.Resources import android.content.res.Resources
import android.os.Build import android.os.Build
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.util.Log import android.util.Log
import android.view.Gravity import android.view.Gravity
import android.view.KeyEvent import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.View.NO_ID import android.view.View.NO_ID
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
@ -30,14 +31,12 @@ import com.google.android.material.chip.ChipGroup
import com.google.android.material.navigationrail.NavigationRailView import com.google.android.material.navigationrail.NavigationRailView
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey 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.mvvm.logError
import com.lagradost.cloudstream3.ui.player.PlayerEventType import com.lagradost.cloudstream3.ui.player.PlayerEventType
import com.lagradost.cloudstream3.ui.result.ResultFragment import com.lagradost.cloudstream3.ui.result.ResultFragment
import com.lagradost.cloudstream3.ui.result.UiText import com.lagradost.cloudstream3.ui.result.UiText
import com.lagradost.cloudstream3.ui.settings.Globals.updateTv import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.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.DataStoreHelper
import com.lagradost.cloudstream3.utils.Event import com.lagradost.cloudstream3.utils.Event
import com.lagradost.cloudstream3.utils.UIHelper import com.lagradost.cloudstream3.utils.UIHelper
@ -100,7 +99,8 @@ object CommonActivity {
var playerEventListener: ((PlayerEventType) -> Unit)? = null var playerEventListener: ((PlayerEventType) -> Unit)? = null
var keyEventListener: ((Pair<KeyEvent?, Boolean>) -> Boolean)? = null var keyEventListener: ((Pair<KeyEvent?, Boolean>) -> Boolean)? = null
private var currentToast: Toast? = null
var currentToast: Toast? = null
fun showToast(@StringRes message: Int, duration: Int? = null) { fun showToast(@StringRes message: Int, duration: Int? = null) {
val act = activity ?: return val act = activity ?: return
@ -156,19 +156,25 @@ object CommonActivity {
} catch (e: Exception) { } catch (e: Exception) {
logError(e) logError(e)
} }
try { try {
val binding = ToastBinding.inflate(act.layoutInflater) val inflater =
binding.text.text = message.trim() act.getSystemService(AppCompatActivity.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val layout: View = inflater.inflate(
R.layout.toast,
act.findViewById<View>(R.id.toast_layout_root) as ViewGroup?
)
val text = layout.findViewById(R.id.text) as TextView
text.text = message.trim()
// custom toasts are deprecated and won't appear when cs3 sets minSDK to api30 (A11)
val toast = Toast(act) val toast = Toast(act)
toast.duration = duration ?: Toast.LENGTH_SHORT
toast.setGravity(Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM, 0, 5.toPx) 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.duration = duration ?: Toast.LENGTH_SHORT
currentToast = toast toast.view = layout
//https://github.com/PureWriter/ToastCompat
toast.show() toast.show()
currentToast = toast
} catch (e: Exception) { } catch (e: Exception) {
logError(e) logError(e)
} }
@ -277,35 +283,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?) { fun loadThemes(act: Activity?) {
if (act == null) return if (act == null) return
val settingsManager = PreferenceManager.getDefaultSharedPreferences(act) val settingsManager = PreferenceManager.getDefaultSharedPreferences(act)
val currentTheme = val currentTheme =
when (settingsManager.getString(act.getString(R.string.app_theme_key), "AmoledLight")) { when (settingsManager.getString(act.getString(R.string.app_theme_key), "AmoledLight")) {
"System" -> mapSystemTheme(act)
"Black" -> R.style.AppTheme "Black" -> R.style.AppTheme
"Light" -> R.style.LightMode "Light" -> R.style.LightMode
"Amoled" -> R.style.AmoledMode "Amoled" -> R.style.AmoledMode
@ -488,6 +471,20 @@ object CommonActivity {
fun onKeyDown(act: Activity?, keyCode: Int, event: KeyEvent?) { 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 // 149 keycode_numpad 5
when (keyCode) { when (keyCode) {

View file

@ -2,7 +2,6 @@ package com.lagradost.cloudstream3
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.RequestBody import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import org.schabi.newpipe.extractor.downloader.Downloader import org.schabi.newpipe.extractor.downloader.Downloader
import org.schabi.newpipe.extractor.downloader.Request import org.schabi.newpipe.extractor.downloader.Request
import org.schabi.newpipe.extractor.downloader.Response import org.schabi.newpipe.extractor.downloader.Response
@ -11,7 +10,7 @@ import java.util.concurrent.TimeUnit
class DownloaderTestImpl private constructor(builder: OkHttpClient.Builder) : Downloader() { 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 { override fun execute(request: Request): Response {
val httpMethod: String = request.httpMethod() val httpMethod: String = request.httpMethod()
val url: String = request.url() val url: String = request.url()
@ -19,7 +18,7 @@ class DownloaderTestImpl private constructor(builder: OkHttpClient.Builder) : Do
val dataToSend: ByteArray? = request.dataToSend() val dataToSend: ByteArray? = request.dataToSend()
var requestBody: RequestBody? = null var requestBody: RequestBody? = null
if (dataToSend != null) { if (dataToSend != null) {
requestBody = dataToSend.toRequestBody(null, 0, dataToSend.size) requestBody = RequestBody.create(null, dataToSend)
} }
val requestBuilder: okhttp3.Request.Builder = okhttp3.Request.Builder() val requestBuilder: okhttp3.Request.Builder = okhttp3.Request.Builder()
.method(httpMethod, requestBody).url(url) .method(httpMethod, requestBody).url(url)
@ -74,4 +73,8 @@ class DownloaderTestImpl private constructor(builder: OkHttpClient.Builder) : Do
return instance return instance
} }
} }
init {
client = builder.readTimeout(30, TimeUnit.SECONDS).build()
}
} }

View file

@ -1,43 +1,48 @@
package com.lagradost.cloudstream3 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.annotation.JsonProperty
import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.json.JsonMapper import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.module.kotlin.kotlinModule import com.fasterxml.jackson.module.kotlin.kotlinModule
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall 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.SyncIdName
import com.lagradost.cloudstream3.syncproviders.providers.SimklApi
import com.lagradost.cloudstream3.ui.player.SubtitleData
import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.utils.AppUtils.toJson 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.mainWork
import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf
import com.lagradost.nicehttp.RequestBodyTypes import com.lagradost.nicehttp.RequestBodyTypes
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import java.net.URI
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi
import kotlin.math.absoluteValue 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 * Defines the constant for the all languages preference, if this is set then it is
* the equivalent of all languages being set * the equivalent of all languages being set
**/ **/
const val AllLanguagesName = "universal" 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 { object APIHolder {
val unixTime: Long val unixTime: Long
get() = System.currentTimeMillis() / 1000L get() = System.currentTimeMillis() / 1000L
@ -108,6 +113,15 @@ object APIHolder {
return null return null
} }
private fun getLoadResponseIdFromUrl(url: String, apiName: String): Int {
return url.replace(getApiFromNameNull(apiName)?.mainUrl ?: "", "").replace("/", "")
.hashCode()
}
fun LoadResponse.getId(): Int {
return getLoadResponseIdFromUrl(url, apiName)
}
/** /**
* Gets the website captcha token * Gets the website captcha token
* discovered originally by https://github.com/ahmedgamal17 * discovered originally by https://github.com/ahmedgamal17
@ -123,9 +137,10 @@ object APIHolder {
// To get the key // To get the key
suspend fun getCaptchaToken(url: String, key: String, referer: String? = null): String? { suspend fun getCaptchaToken(url: String, key: String, referer: String? = null): String? {
try { try {
val uri = URI.create(url) val uri = Uri.parse(url)
val domain = base64Encode( val domain = encodeToString(
(uri.scheme + "://" + uri.host + ":443").encodeToByteArray(), (uri.scheme + "://" + uri.host + ":443").encodeToByteArray(),
0
).replace("\n", "").replace("=", ".") ).replace("\n", "").replace("=", ".")
val vToken = val vToken =
@ -208,12 +223,7 @@ object APIHolder {
if(lessAccurate) matchingTitles || matchingTypes && matchingYears else matchingTitles && matchingTypes && matchingYears if(lessAccurate) matchingTitles || matchingTypes && matchingYears else matchingTitles && matchingTypes && matchingYears
} ?: return null } ?: return null
Tracker( Tracker(res.idMal, res.id.toString(), res.coverImage?.extraLarge ?: res.coverImage?.large, res.bannerImage)
res.idMal,
res.id.toString(),
res.coverImage?.extraLarge ?: res.coverImage?.large,
res.bannerImage
)
} catch (t: Throwable) { } catch (t: Throwable) {
logError(t) logError(t)
null null
@ -260,6 +270,165 @@ object APIHolder {
return app.post("https://graphql.anilist.co", requestBody = data) return app.post("https://graphql.anilist.co", requestBody = data)
.parsedSafe() .parsedSafe()
} }
fun Context.getApiSettings(): HashSet<String> {
//val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
val hashSet = HashSet<String>()
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<String>()
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<DubStatus> {
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
val hashSet = HashSet<DubStatus>()
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<String> {
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<TvType> {
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
val hashSet = HashSet<TvType>()
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<MainAPI> {
// 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<SearchResponse>): List<SearchResponse> {
// 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 +617,7 @@ abstract class MainAPI {
/**Used for testing and can be used to disable the providers if WebView is not available*/ /**Used for testing and can be used to disable the providers if WebView is not available*/
open val usesWebView = false 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 var sourcePlugin: String? = null
open val hasMainPage = false open val hasMainPage = false
@ -482,7 +651,7 @@ abstract class MainAPI {
//emptyList<MainPageData>() // //emptyList<MainPageData>() //
open val mainPage = listOf(MainPageData("", "", false)) open val mainPage = listOf(MainPageData("", "", false))
// @WorkerThread @WorkerThread
open suspend fun getMainPage( open suspend fun getMainPage(
page: Int, page: Int,
request: MainPageRequest, request: MainPageRequest,
@ -490,17 +659,17 @@ abstract class MainAPI {
throw NotImplementedError() throw NotImplementedError()
} }
// @WorkerThread @WorkerThread
open suspend fun search(query: String): List<SearchResponse>? { open suspend fun search(query: String): List<SearchResponse>? {
throw NotImplementedError() throw NotImplementedError()
} }
// @WorkerThread @WorkerThread
open suspend fun quickSearch(query: String): List<SearchResponse>? { open suspend fun quickSearch(query: String): List<SearchResponse>? {
throw NotImplementedError() throw NotImplementedError()
} }
// @WorkerThread @WorkerThread
/** /**
* Based on data from search() or getMainPage() it generates a LoadResponse, * Based on data from search() or getMainPage() it generates a LoadResponse,
* basically opening the info page from a link. * basically opening the info page from a link.
@ -518,13 +687,13 @@ abstract class MainAPI {
* This function might be updated to include exoplayer timestamps etc in the future * This function might be updated to include exoplayer timestamps etc in the future
* if the need arises. * if the need arises.
* */ * */
// @WorkerThread @WorkerThread
open suspend fun extractorVerifierJob(extractorData: String?) { open suspend fun extractorVerifierJob(extractorData: String?) {
throw NotImplementedError() throw NotImplementedError()
} }
/**Callback is fired once a link is found, will return true if method is executed successfully*/ /**Callback is fired once a link is found, will return true if method is executed successfully*/
// @WorkerThread @WorkerThread
open suspend fun loadLinks( open suspend fun loadLinks(
data: String, data: String,
isCasting: Boolean, isCasting: Boolean,
@ -549,17 +718,30 @@ abstract class MainAPI {
} }
/** Might need a different implementation for desktop*/ /** Might need a different implementation for desktop*/
@SuppressLint("NewApi")
fun base64Decode(string: String): String { fun base64Decode(string: String): String {
return String(base64DecodeArray(string), Charsets.ISO_8859_1) return String(base64DecodeArray(string), Charsets.ISO_8859_1)
} }
@OptIn(ExperimentalEncodingApi::class)
@SuppressLint("NewApi")
fun base64DecodeArray(string: String): ByteArray { 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 { 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? { fun MainAPI.fixUrlNull(url: String?): String? {
if (url.isNullOrEmpty()) { if (url.isNullOrEmpty()) {
@ -594,6 +776,10 @@ fun sortUrls(urls: Set<ExtractorLink>): List<ExtractorLink> {
return urls.sortedBy { t -> -t.quality } return urls.sortedBy { t -> -t.quality }
} }
fun sortSubs(subs: Set<SubtitleData>): List<SubtitleData> {
return subs.sortedBy { it.name }
}
fun capitalizeString(str: String): String { fun capitalizeString(str: String): String {
return capitalizeStringNullable(str) ?: str return capitalizeStringNullable(str) ?: str
} }
@ -677,12 +863,7 @@ enum class TvType(value: Int?) {
AsianDrama(9), AsianDrama(9),
Live(10), Live(10),
NSFW(11), NSFW(11),
Others(12), Others(12)
Music(13),
AudioBook(14),
/** Wont load the built in player, make your own interaction */
CustomMedia(15),
} }
public enum class AutoDownloadMode(val value: Int) { public enum class AutoDownloadMode(val value: Int) {
@ -1015,25 +1196,11 @@ interface LoadResponse {
var contentRating: String? var contentRating: String?
companion object { companion object {
var malIdPrefix = "" //malApi.idPrefix private val malIdPrefix = malApi.idPrefix
var aniListIdPrefix = "" //aniListApi.idPrefix private val aniListIdPrefix = aniListApi.idPrefix
var simklIdPrefix = "" //simklApi.idPrefix private val simklIdPrefix = simklApi.idPrefix
var isTrailersEnabled = true 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<SimklSyncServices, String> {
return tryParseJson(idString) ?: return emptyMap()
}
fun LoadResponse.isMovie(): Boolean { fun LoadResponse.isMovie(): Boolean {
return this.type.isMovieType() || this is MovieLoadResponse return this.type.isMovieType() || this is MovieLoadResponse
} }
@ -1057,12 +1224,12 @@ interface LoadResponse {
* Internal helper function to add simkl ids from other databases. * Internal helper function to add simkl ids from other databases.
*/ */
private fun LoadResponse.addSimklId( private fun LoadResponse.addSimklId(
database: SimklSyncServices, database: SimklApi.Companion.SyncServices,
id: String? id: String?
) { ) {
normalSafeApiCall { normalSafeApiCall {
this.syncData[simklIdPrefix] = this.syncData[simklIdPrefix] =
addIdToString(this.syncData[simklIdPrefix], database, id.toString()) SimklApi.addIdToString(this.syncData[simklIdPrefix], database, id.toString())
?: return@normalSafeApiCall ?: return@normalSafeApiCall
} }
} }
@ -1082,28 +1249,28 @@ interface LoadResponse {
fun LoadResponse.getImdbId(): String? { fun LoadResponse.getImdbId(): String? {
return normalSafeApiCall { return normalSafeApiCall {
readIdFromString(this.syncData[simklIdPrefix])[SimklSyncServices.Imdb] SimklApi.readIdFromString(this.syncData[simklIdPrefix])?.get(SimklApi.Companion.SyncServices.Imdb)
} }
} }
fun LoadResponse.getTMDbId(): String? { fun LoadResponse.getTMDbId(): String? {
return normalSafeApiCall { return normalSafeApiCall {
readIdFromString(this.syncData[simklIdPrefix])[SimklSyncServices.Tmdb] SimklApi.readIdFromString(this.syncData[simklIdPrefix])?.get(SimklApi.Companion.SyncServices.Tmdb)
} }
} }
fun LoadResponse.addMalId(id: Int?) { fun LoadResponse.addMalId(id: Int?) {
this.syncData[malIdPrefix] = (id ?: return).toString() 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?) { fun LoadResponse.addAniListId(id: Int?) {
this.syncData[aniListIdPrefix] = (id ?: return).toString() 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?) { fun LoadResponse.addSimklId(id: Int?) {
this.addSimklId(SimklSyncServices.Simkl, id.toString()) this.addSimklId(SimklApi.Companion.SyncServices.Simkl, id.toString())
} }
fun LoadResponse.addImdbUrl(url: String?) { fun LoadResponse.addImdbUrl(url: String?) {
@ -1185,7 +1352,7 @@ interface LoadResponse {
fun LoadResponse.addImdbId(id: String?) { fun LoadResponse.addImdbId(id: String?) {
// TODO add imdb sync // TODO add imdb sync
this.addSimklId(SimklSyncServices.Imdb, id) this.addSimklId(SimklApi.Companion.SyncServices.Imdb, id)
} }
fun LoadResponse.addTrackId(id: String?) { fun LoadResponse.addTrackId(id: String?) {
@ -1198,7 +1365,7 @@ interface LoadResponse {
fun LoadResponse.addTMDbId(id: String?) { fun LoadResponse.addTMDbId(id: String?) {
// TODO add TMDb sync // TODO add TMDb sync
this.addSimklId(SimklSyncServices.Tmdb, id) this.addSimklId(SimklApi.Companion.SyncServices.Tmdb, id)
} }
fun LoadResponse.addRating(text: String?) { fun LoadResponse.addRating(text: String?) {
@ -1277,24 +1444,11 @@ fun TvType?.isEpisodeBased(): Boolean {
return (this == TvType.TvSeries || this == TvType.Anime || this == TvType.AsianDrama) return (this == TvType.TvSeries || this == TvType.Anime || this == TvType.AsianDrama)
} }
data class NextAiring( data class NextAiring(
val episode: Int, val episode: Int,
val unixTime: Long, 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 * @param season To be mapped with episode season, not shown in UI if displaySeason is defined
@ -1385,26 +1539,8 @@ data class TorrentLoadResponse(
posterHeaders: Map<String, String>? = null, posterHeaders: Map<String, String>? = null,
backgroundPosterUrl: String? = null, backgroundPosterUrl: String? = null,
) : this( ) : this(
name, name, url, apiName, magnet, torrent, plot, type, posterUrl, year, rating, tags, duration, trailers,
url, recommendations, actors, comingSoon, syncData, posterHeaders, backgroundPosterUrl, null
apiName,
magnet,
torrent,
plot,
type,
posterUrl,
year,
rating,
tags,
duration,
trailers,
recommendations,
actors,
comingSoon,
syncData,
posterHeaders,
backgroundPosterUrl,
null
) )
} }
@ -1456,8 +1592,7 @@ data class AnimeLoadResponse(
return this.episodes.maxOf { (_, episodes) -> return this.episodes.maxOf { (_, episodes) ->
episodes.count { episodeData -> episodes.count { episodeData ->
// Prioritize display season as actual season may be something random to fit multiple seasons into one. // Prioritize display season as actual season may be something random to fit multiple seasons into one.
val episodeSeason = val episodeSeason = displayMap[episodeData.season] ?: episodeData.season ?: Int.MIN_VALUE
displayMap[episodeData.season] ?: episodeData.season ?: Int.MIN_VALUE
// Count all episodes from season 1 to below the current season. // Count all episodes from season 1 to below the current season.
episodeSeason in 1..<season episodeSeason in 1..<season
} }
@ -1494,31 +1629,9 @@ data class AnimeLoadResponse(
seasonNames: List<SeasonData>? = null, seasonNames: List<SeasonData>? = null,
backgroundPosterUrl: String? = null, backgroundPosterUrl: String? = null,
) : this( ) : this(
engName, engName, japName, name, url, apiName, type, posterUrl, year, episodes, showStatus, plot, tags,
japName, synonyms, rating, duration, trailers, recommendations, actors, comingSoon, syncData, posterHeaders,
name, nextAiring, seasonNames, backgroundPosterUrl, null
url,
apiName,
type,
posterUrl,
year,
episodes,
showStatus,
plot,
tags,
synonyms,
rating,
duration,
trailers,
recommendations,
actors,
comingSoon,
syncData,
posterHeaders,
nextAiring,
seasonNames,
backgroundPosterUrl,
null
) )
} }
@ -1700,17 +1813,7 @@ suspend fun MainAPI.newMovieLoadResponse(
builder.initializer() builder.initializer()
return builder 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( data class Episode(
var data: String, var data: String,
var name: String? = null, var name: String? = null,
@ -1720,25 +1823,7 @@ data class Episode(
var rating: Int? = null, var rating: Int? = null,
var description: String? = null, var description: String? = null,
var date: Long? = 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") { fun Episode.addDate(date: String?, format: String = "yyyy-MM-dd") {
try { try {
@ -1780,28 +1865,6 @@ fun <T> MainAPI.newEpisode(
return builder return builder
} }
interface IDownloadableMinimum {
val url: String
val referer: String
val headers: Map<String, String>
}
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( data class TvSeriesLoadResponse(
override var name: String, override var name: String,
override var url: String, override var url: String,
@ -1843,8 +1906,7 @@ data class TvSeriesLoadResponse(
return episodes.count { episodeData -> return episodes.count { episodeData ->
// Prioritize display season as actual season may be something random to fit multiple seasons into one. // Prioritize display season as actual season may be something random to fit multiple seasons into one.
val episodeSeason = val episodeSeason = displayMap[episodeData.season] ?: episodeData.season ?: Int.MIN_VALUE
displayMap[episodeData.season] ?: episodeData.season ?: Int.MIN_VALUE
// Count all episodes from season 1 to below the current season. // Count all episodes from season 1 to below the current season.
episodeSeason in 1..<season episodeSeason in 1..<season
} + episode } + episode
@ -1877,28 +1939,9 @@ data class TvSeriesLoadResponse(
seasonNames: List<SeasonData>? = null, seasonNames: List<SeasonData>? = null,
backgroundPosterUrl: String? = null, backgroundPosterUrl: String? = null,
) : this( ) : this(
name, name, url, apiName, type, episodes, posterUrl, year, plot, showStatus, rating, tags, duration,
url, trailers, recommendations, actors, comingSoon, syncData, posterHeaders, nextAiring, seasonNames,
apiName, backgroundPosterUrl, null
type,
episodes,
posterUrl,
year,
plot,
showStatus,
rating,
tags,
duration,
trailers,
recommendations,
actors,
comingSoon,
syncData,
posterHeaders,
nextAiring,
seasonNames,
backgroundPosterUrl,
null
) )
} }
@ -1962,7 +2005,6 @@ data class AniSearch(
@JsonProperty("extraLarge") var extraLarge: String? = null, @JsonProperty("extraLarge") var extraLarge: String? = null,
@JsonProperty("large") var large: String? = null, @JsonProperty("large") var large: String? = null,
) )
data class Title( data class Title(
@JsonProperty("romaji") var romaji: String? = null, @JsonProperty("romaji") var romaji: String? = null,
@JsonProperty("english") var english: String? = null, @JsonProperty("english") var english: String? = null,

View file

@ -19,6 +19,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.WindowManager import android.view.WindowManager
import android.widget.Toast import android.widget.Toast
import android.widget.Toast.LENGTH_LONG
import androidx.activity.OnBackPressedCallback import androidx.activity.OnBackPressedCallback
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.IdRes import androidx.annotation.IdRes
@ -44,6 +45,9 @@ import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.LinearSnapHelper import androidx.recyclerview.widget.LinearSnapHelper
import androidx.recyclerview.widget.RecyclerView 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.CastContext
import com.google.android.gms.cast.framework.Session import com.google.android.gms.cast.framework.Session
import com.google.android.gms.cast.framework.SessionManager import com.google.android.gms.cast.framework.SessionManager
@ -56,7 +60,9 @@ import com.google.common.collect.Comparators.min
import com.jaredrummler.android.colorpicker.ColorPickerDialogListener import com.jaredrummler.android.colorpicker.ColorPickerDialogListener
import com.lagradost.cloudstream3.APIHolder.allProviders import com.lagradost.cloudstream3.APIHolder.allProviders
import com.lagradost.cloudstream3.APIHolder.apis import com.lagradost.cloudstream3.APIHolder.apis
import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings
import com.lagradost.cloudstream3.APIHolder.initAll 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.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
@ -68,7 +74,6 @@ import com.lagradost.cloudstream3.CommonActivity.screenHeight
import com.lagradost.cloudstream3.CommonActivity.setActivityInstance import com.lagradost.cloudstream3.CommonActivity.setActivityInstance
import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.CommonActivity.updateLocale import com.lagradost.cloudstream3.CommonActivity.updateLocale
import com.lagradost.cloudstream3.CommonActivity.updateTheme
import com.lagradost.cloudstream3.databinding.ActivityMainBinding import com.lagradost.cloudstream3.databinding.ActivityMainBinding
import com.lagradost.cloudstream3.databinding.ActivityMainTvBinding import com.lagradost.cloudstream3.databinding.ActivityMainTvBinding
import com.lagradost.cloudstream3.databinding.BottomResultviewPreviewBinding import com.lagradost.cloudstream3.databinding.BottomResultviewPreviewBinding
@ -82,23 +87,20 @@ import com.lagradost.cloudstream3.plugins.PluginManager
import com.lagradost.cloudstream3.plugins.PluginManager.loadAllOnlinePlugins import com.lagradost.cloudstream3.plugins.PluginManager.loadAllOnlinePlugins
import com.lagradost.cloudstream3.plugins.PluginManager.loadSinglePlugin import com.lagradost.cloudstream3.plugins.PluginManager.loadSinglePlugin
import com.lagradost.cloudstream3.receivers.VideoDownloadRestartReceiver 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.OAuth2Apis
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.accountManagers 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.inAppAuths
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.localListApi
import com.lagradost.cloudstream3.syncproviders.SyncAPI import com.lagradost.cloudstream3.syncproviders.SyncAPI
import com.lagradost.cloudstream3.ui.APIRepository import com.lagradost.cloudstream3.ui.APIRepository
import com.lagradost.cloudstream3.ui.SyncWatchType import com.lagradost.cloudstream3.ui.SyncWatchType
import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.WatchType
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO
import com.lagradost.cloudstream3.ui.home.HomeViewModel 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.BasicLink
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
import com.lagradost.cloudstream3.ui.player.LinkGenerator import com.lagradost.cloudstream3.ui.player.LinkGenerator
@ -108,40 +110,32 @@ import com.lagradost.cloudstream3.ui.result.START_ACTION_RESUME_LATEST
import com.lagradost.cloudstream3.ui.result.SyncViewModel import com.lagradost.cloudstream3.ui.result.SyncViewModel
import com.lagradost.cloudstream3.ui.result.setImage import com.lagradost.cloudstream3.ui.result.setImage
import com.lagradost.cloudstream3.ui.result.setText 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.result.txt
import com.lagradost.cloudstream3.ui.search.SearchFragment import com.lagradost.cloudstream3.ui.search.SearchFragment
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings
import com.lagradost.cloudstream3.ui.settings.Globals.PHONE import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTruePhone
import com.lagradost.cloudstream3.ui.settings.Globals.TV import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.ui.settings.Globals.updateTv import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.updateTv
import com.lagradost.cloudstream3.ui.settings.SettingsGeneral import com.lagradost.cloudstream3.ui.settings.SettingsGeneral
import com.lagradost.cloudstream3.ui.setup.HAS_DONE_SETUP_KEY import com.lagradost.cloudstream3.ui.setup.HAS_DONE_SETUP_KEY
import com.lagradost.cloudstream3.ui.setup.SetupFragmentExtensions import com.lagradost.cloudstream3.ui.setup.SetupFragmentExtensions
import com.lagradost.cloudstream3.utils.ApkInstaller import com.lagradost.cloudstream3.utils.ApkInstaller
import com.lagradost.cloudstream3.utils.AppContextUtils.getApiDubstatusSettings import com.lagradost.cloudstream3.utils.AppUtils.html
import com.lagradost.cloudstream3.utils.AppContextUtils.html import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable
import com.lagradost.cloudstream3.utils.AppContextUtils.isCastApiAvailable import com.lagradost.cloudstream3.utils.AppUtils.isLtr
import com.lagradost.cloudstream3.utils.AppContextUtils.isLtr import com.lagradost.cloudstream3.utils.AppUtils.isNetworkAvailable
import com.lagradost.cloudstream3.utils.AppContextUtils.isNetworkAvailable import com.lagradost.cloudstream3.utils.AppUtils.isRtl
import com.lagradost.cloudstream3.utils.AppContextUtils.isRtl import com.lagradost.cloudstream3.utils.AppUtils.loadCache
import com.lagradost.cloudstream3.utils.AppContextUtils.loadCache import com.lagradost.cloudstream3.utils.AppUtils.loadRepository
import com.lagradost.cloudstream3.utils.AppContextUtils.loadRepository import com.lagradost.cloudstream3.utils.AppUtils.loadResult
import com.lagradost.cloudstream3.utils.AppContextUtils.loadResult import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
import com.lagradost.cloudstream3.utils.AppContextUtils.loadSearchResult import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus
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.BackupUtils.backup import com.lagradost.cloudstream3.utils.BackupUtils.backup
import com.lagradost.cloudstream3.utils.BackupUtils.setUpBackup import com.lagradost.cloudstream3.utils.BackupUtils.setUpBackup
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.BiometricCallback import com.lagradost.cloudstream3.utils.BiometricAuthenticator
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.biometricPrompt
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.deviceHasPasswordPinLock 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.BiometricAuthenticator.startBiometricAuthentication
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.Coroutines.main 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.Event
import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog 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.changeStatusBarState
import com.lagradost.cloudstream3.utils.UIHelper.checkWrite import com.lagradost.cloudstream3.utils.UIHelper.checkWrite
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute 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.UIHelper.toPx
import com.lagradost.cloudstream3.utils.USER_PROVIDER_API import com.lagradost.cloudstream3.utils.USER_PROVIDER_API
import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_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 com.lagradost.safefile.SafeFile
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
@ -176,6 +170,7 @@ import java.net.URLDecoder
import java.nio.charset.Charset import java.nio.charset.Charset
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
import kotlin.reflect.KClass
import kotlin.system.exitProcess import kotlin.system.exitProcess
//https://github.com/videolan/vlc-android/blob/3706c4be2da6800b3d26344fc04fab03ffa4b860/application/vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerActivity.kt#L1898 //https://github.com/videolan/vlc-android/blob/3706c4be2da6800b3d26344fc04fab03ffa4b860/application/vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerActivity.kt#L1898
@ -188,8 +183,6 @@ import kotlin.system.exitProcess
//https://github.com/jellyfin/jellyfin-android/blob/6cbf0edf84a3da82347c8d59b5d5590749da81a9/app/src/main/java/org/jellyfin/mobile/bridge/ExternalPlayer.kt#L225 //https://github.com/jellyfin/jellyfin-android/blob/6cbf0edf84a3da82347c8d59b5d5590749da81a9/app/src/main/java/org/jellyfin/mobile/bridge/ExternalPlayer.kt#L225
class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCallback {
companion object {
const val VLC_PACKAGE = "org.videolan.vlc" const val VLC_PACKAGE = "org.videolan.vlc"
const val MPV_PACKAGE = "is.xyz.mpv" const val MPV_PACKAGE = "is.xyz.mpv"
const val WEB_VIDEO_CAST_PACKAGE = "com.instantbits.cast.webvideo" const val WEB_VIDEO_CAST_PACKAGE = "com.instantbits.cast.webvideo"
@ -257,13 +250,11 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
duration = "duration", duration = "duration",
) { ) {
override fun getPosition(intent: Intent?): Long { override fun getPosition(intent: Intent?): Long {
return intent?.getIntExtra(this.position, defaultTime.toInt())?.toLong() return intent?.getIntExtra(this.position, defaultTime.toInt())?.toLong() ?: defaultTime
?: defaultTime
} }
override fun getDuration(intent: Intent?): Long { override fun getDuration(intent: Intent?): Long {
return intent?.getIntExtra(this.duration, defaultTime.toInt())?.toLong() return intent?.getIntExtra(this.duration, defaultTime.toInt())?.toLong() ?: defaultTime
?: defaultTime
} }
} }
@ -273,7 +264,35 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
VLC, MPV, WEB_VIDEO 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 <T : Any> parse(text: String, kClass: KClass<T>): T {
return mapper.readValue(text, kClass.java)
}
override fun <T : Any> parseSafe(text: String, kClass: KClass<T>): 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 TAG = "MAINACT" const val TAG = "MAINACT"
const val ANIMATED_OUTLINE: Boolean = false const val ANIMATED_OUTLINE: Boolean = false
var lastError: String? = null var lastError: String? = null
@ -318,12 +337,10 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
// kinda shitty solution, but cant com main->home otherwise for popups // kinda shitty solution, but cant com main->home otherwise for popups
val bookmarksUpdatedEvent = Event<Boolean>() val bookmarksUpdatedEvent = Event<Boolean>()
/** /**
* Used by DataStoreHelper to fully reload home when switching accounts * Used by DataStoreHelper to fully reload home when switching accounts
*/ */
val reloadHomeEvent = Event<Boolean>() val reloadHomeEvent = Event<Boolean>()
/** /**
* Used by DataStoreHelper to fully reload library when switching accounts * Used by DataStoreHelper to fully reload library when switching accounts
*/ */
@ -351,7 +368,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
println("Repository url: $realUrl") println("Repository url: $realUrl")
loadRepository(realUrl) loadRepository(realUrl)
return true return true
} else if (str.contains(APP_STRING)) { } else if (str.contains(appString)) {
for (api in OAuth2Apis) { for (api in OAuth2Apis) {
if (str.contains("/${api.redirectUrl}")) { if (str.contains("/${api.redirectUrl}")) {
ioSafe { ioSafe {
@ -381,15 +398,15 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
} }
// This specific intent is used for the gradle deployWithAdb // 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 // 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) PluginManager.hotReloadAllLocalPlugins(activity)
} }
} else if (safeURI(str)?.scheme == APP_STRING_REPO) { } else if (safeURI(str)?.scheme == appStringRepo) {
val url = str.replaceFirst(APP_STRING_REPO, "https") val url = str.replaceFirst(appStringRepo, "https")
loadRepository(url) loadRepository(url)
return true return true
} else if (safeURI(str)?.scheme == APP_STRING_SEARCH) { } else if (safeURI(str)?.scheme == appStringSearch) {
val query = str.substringAfter("$APP_STRING_SEARCH://") val query = str.substringAfter("$appStringSearch://")
nextSearchQuery = nextSearchQuery =
try { try {
URLDecoder.decode(query, "UTF-8") URLDecoder.decode(query, "UTF-8")
@ -403,7 +420,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
R.id.navigation_search R.id.navigation_search
activity?.findViewById<NavigationRailView>(R.id.nav_rail_view)?.selectedItemId = activity?.findViewById<NavigationRailView>(R.id.nav_rail_view)?.selectedItemId =
R.id.navigation_search R.id.navigation_search
} else if (safeURI(str)?.scheme == APP_STRING_PLAYER) { } else if (safeURI(str)?.scheme == appStringPlayer) {
val uri = Uri.parse(str) val uri = Uri.parse(str)
val name = uri.getQueryParameter("name") val name = uri.getQueryParameter("name")
val url = URLDecoder.decode(uri.authority, "UTF-8") val url = URLDecoder.decode(uri.authority, "UTF-8")
@ -417,9 +434,9 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
) )
) )
) )
} else if (safeURI(str)?.scheme == APP_STRING_RESUME_WATCHING) { } else if (safeURI(str)?.scheme == appStringResumeWatching) {
val id = val id =
str.substringAfter("$APP_STRING_RESUME_WATCHING://").toIntOrNull() str.substringAfter("$appStringResumeWatching://").toIntOrNull()
?: return false ?: return false
ioSafe { ioSafe {
val resumeWatchingCard = val resumeWatchingCard =
@ -473,7 +490,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
) DubStatus.Dubbed else DubStatus.Subbed, null ) DubStatus.Dubbed else DubStatus.Subbed, null
) )
}else { }else {
viewModel.loadSmall(result) viewModel.loadSmall(this,result)
} }
} }
@ -488,7 +505,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
override fun onConfigurationChanged(newConfig: Configuration) { override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig) super.onConfigurationChanged(newConfig)
updateLocale() // android fucks me by chaining lang when rotating the phone updateLocale() // android fucks me by chaining lang when rotating the phone
updateTheme(this) // Update if system theme
val navHostFragment = val navHostFragment =
supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
@ -539,7 +555,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
binding?.navHostFragment?.apply { binding?.navHostFragment?.apply {
val params = layoutParams as ConstraintLayout.LayoutParams val params = layoutParams as ConstraintLayout.LayoutParams
val push = val push =
if (!dontPush && isLayout(TV or EMULATOR)) resources.getDimensionPixelSize(R.dimen.navbar_width) else 0 if (!dontPush && isTvSettings()) resources.getDimensionPixelSize(R.dimen.navbar_width) else 0
if (!this.isLtr()) { if (!this.isLtr()) {
params.setMargins( params.setMargins(
@ -566,51 +582,30 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
} }
Configuration.ORIENTATION_PORTRAIT -> { Configuration.ORIENTATION_PORTRAIT -> {
isLayout(TV or EMULATOR) isTvSettings()
} }
else -> { else -> {
false false
} }
} }
binding?.apply { binding?.apply {
navRailView.isVisible = isNavVisible && landscape
navView.isVisible = isNavVisible && !landscape navView.isVisible = isNavVisible && !landscape
navRailView.isVisible = isNavVisible && landscape
/** // Hide library on TV since it is not supported yet :(
* We need to make sure if we return to a sub-fragment, //val isTrueTv = isTrueTvSettings()
* the correct navigation item is selected so that it does not //navView.menu.findItem(R.id.navigation_library)?.isVisible = !isTrueTv
* highlight the wrong one in UI. //navRailView.menu.findItem(R.id.navigation_library)?.isVisible = !isTrueTv
*/
when (destination.id) { // Hide downloads on TV
in listOf(R.id.navigation_downloads, R.id.navigation_download_child) -> { //navView.menu.findItem(R.id.navigation_downloads)?.isVisible = !isTrueTv
navRailView.menu.findItem(R.id.navigation_downloads).isChecked = true //navRailView.menu.findItem(R.id.navigation_downloads)?.isVisible = !isTrueTv
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
}
}
} }
} }
//private var mCastSession: CastSession? = null //private var mCastSession: CastSession? = null
var mSessionManager: SessionManager? = null lateinit var mSessionManager: SessionManager
private val mSessionManagerListener: SessionManagerListener<Session> by lazy { SessionManagerListenerImpl() } private val mSessionManagerListener: SessionManagerListener<Session> by lazy { SessionManagerListenerImpl() }
private inner class SessionManagerListenerImpl : SessionManagerListener<Session> { private inner class SessionManagerListenerImpl : SessionManagerListener<Session> {
@ -650,7 +645,8 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
setActivityInstance(this) setActivityInstance(this)
try { try {
if (isCastApiAvailable()) { if (isCastApiAvailable()) {
mSessionManager?.addSessionManagerListener(mSessionManagerListener) //mCastSession = mSessionManager.currentCastSession
mSessionManager.addSessionManagerListener(mSessionManagerListener)
} }
} catch (e: Exception) { } catch (e: Exception) {
logError(e) logError(e)
@ -666,7 +662,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
} }
try { try {
if (isCastApiAvailable()) { if (isCastApiAvailable()) {
mSessionManager?.removeSessionManagerListener(mSessionManagerListener) mSessionManager.removeSessionManagerListener(mSessionManagerListener)
//mCastSession = null //mCastSession = null
} }
} catch (e: Exception) { } catch (e: Exception) {
@ -674,7 +670,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
} }
} }
override fun dispatchKeyEvent(event: KeyEvent): Boolean { override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
val response = CommonActivity.dispatchKeyEvent(this, event) val response = CommonActivity.dispatchKeyEvent(this, event)
if (response != null) if (response != null)
return response return response
@ -770,7 +766,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
list.forEach { custom -> list.forEach { custom ->
allProviders.firstOrNull { it.javaClass.simpleName == custom.parentJavaClass } allProviders.firstOrNull { it.javaClass.simpleName == custom.parentJavaClass }
?.let { ?.let {
allProviders.add(it.javaClass.getDeclaredConstructor().newInstance().apply { allProviders.add(it.javaClass.newInstance().apply {
name = custom.name name = custom.name
lang = custom.lang lang = custom.lang
mainUrl = custom.url.trimEnd('/') mainUrl = custom.url.trimEnd('/')
@ -793,14 +789,13 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
lateinit var viewModel: ResultViewModel2 lateinit var viewModel: ResultViewModel2
lateinit var syncViewModel : SyncViewModel 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*/ /** kinda dirty, however it signals that we should use the watch status as sync or not*/
var isLocalList : Boolean = false var isLocalList : Boolean = false
override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? { override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {
viewModel =
viewModel = ViewModelProvider(this)[ResultViewModel2::class.java] ViewModelProvider(this)[ResultViewModel2::class.java]
syncViewModel = ViewModelProvider(this)[SyncViewModel::class.java] syncViewModel =
ViewModelProvider(this)[SyncViewModel::class.java]
return super.onCreateView(name, context, attrs) return super.onCreateView(name, context, attrs)
} }
@ -1151,7 +1146,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
try { try {
if (isCastApiAvailable()) { if (isCastApiAvailable()) {
CastContext.getSharedInstance(this) {it.run()}.addOnSuccessListener { mSessionManager = it.sessionManager } mSessionManager = CastContext.getSharedInstance(this).sessionManager
} }
} catch (t: Throwable) { } catch (t: Throwable) {
logError(t) logError(t)
@ -1178,11 +1173,11 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
// just in case, MAIN SHOULD *NEVER* BOOT LOOP CRASH // just in case, MAIN SHOULD *NEVER* BOOT LOOP CRASH
binding = try { binding = try {
if (isLayout(TV or EMULATOR)) { if (isTvSettings()) {
val newLocalBinding = ActivityMainTvBinding.inflate(layoutInflater, null, false) val newLocalBinding = ActivityMainTvBinding.inflate(layoutInflater, null, false)
setContentView(newLocalBinding.root) setContentView(newLocalBinding.root)
if (isLayout(TV) && ANIMATED_OUTLINE) { if (isTrueTvSettings() && ANIMATED_OUTLINE) {
TvFocus.focusOutline = WeakReference(newLocalBinding.focusOutline) TvFocus.focusOutline = WeakReference(newLocalBinding.focusOutline)
newLocalBinding.root.viewTreeObserver.addOnScrollChangedListener { newLocalBinding.root.viewTreeObserver.addOnScrollChangedListener {
TvFocus.updateFocusView(TvFocus.lastFocus.get(), same = true) TvFocus.updateFocusView(TvFocus.lastFocus.get(), same = true)
@ -1194,26 +1189,8 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
newLocalBinding.focusOutline.isVisible = false newLocalBinding.focusOutline.isVisible = false
} }
if (isLayout(TV)) { if (isTrueTvSettings()) {
// Put here any button you don't want focusing it to center the view
val exceptionButtons = listOf(
R.id.home_preview_play_btt,
R.id.home_preview_info_btt,
R.id.home_preview_hidden_next_focus,
R.id.home_preview_hidden_prev_focus,
R.id.result_play_movie_button,
R.id.result_play_series_button,
R.id.result_resume_series_button,
R.id.result_play_trailer_button,
R.id.result_bookmark_Button,
R.id.result_favorite_Button,
R.id.result_subscribe_Button,
R.id.result_search_Button,
R.id.result_episodes_show_button,
)
newLocalBinding.root.viewTreeObserver.addOnGlobalFocusChangeListener { _, newFocus -> newLocalBinding.root.viewTreeObserver.addOnGlobalFocusChangeListener { _, newFocus ->
if (exceptionButtons.contains(newFocus?.id)) return@addOnGlobalFocusChangeListener
centerView(newFocus) centerView(newFocus)
} }
} }
@ -1229,24 +1206,24 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
null null
} }
changeStatusBarState(isLayout(EMULATOR)) changeStatusBarState(isEmulatorSettings())
/** Biometric stuff for users without accounts **/ /** Biometric stuff for users without accounts **/
val noAccounts = settingsManager.getBoolean( val authEnabled = settingsManager.getBoolean(getString(R.string.biometric_key), false)
getString(R.string.skip_startup_account_select_key), val noAccounts = settingsManager.getBoolean(getString(R.string.skip_startup_account_select_key), false) || accounts.count() <= 1
false
) || accounts.count() <= 1
if (isLayout(PHONE) && isAuthEnabled(this) && noAccounts) { if (isTruePhone() && authEnabled && noAccounts) {
if (deviceHasPasswordPinLock(this)) { if (deviceHasPasswordPinLock(this)) {
startBiometricAuthentication(this, R.string.biometric_authentication_title, false) startBiometricAuthentication(this, R.string.biometric_authentication_title, false)
promptInfo?.let { prompt -> BiometricAuthenticator.promptInfo?.let {
biometricPrompt?.authenticate(prompt) BiometricAuthenticator.biometricPrompt?.authenticate(it)
} }
// hide background while authenticating, Sorry moms & dads 🙏 // hide background while authenticating, Sorry moms & dads 🙏
binding?.navHostFragment?.isInvisible = true binding?.navHostFragment?.isInvisible = true
} else {
showToast(R.string.phone_not_secured, LENGTH_LONG)
} }
} }
@ -1257,12 +1234,17 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
this.setKey(getString(R.string.jsdelivr_proxy_key), false) this.setKey(getString(R.string.jsdelivr_proxy_key), false)
} else { } else {
this.setKey(getString(R.string.jsdelivr_proxy_key), true) this.setKey(getString(R.string.jsdelivr_proxy_key), true)
showSnackbar( val parentView: View = findViewById(android.R.id.content)
this@MainActivity, Snackbar.make(parentView, R.string.jsdelivr_enabled, Snackbar.LENGTH_LONG)
R.string.jsdelivr_enabled, .let { snackbar ->
Snackbar.LENGTH_LONG, snackbar.setAction(R.string.revert) {
R.string.revert setKey(getString(R.string.jsdelivr_proxy_key), false)
) { 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()
}
} }
} }
} }
@ -1363,41 +1345,12 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
} }
} }
fun setSubscribeStatus(state: Boolean?) { observe(viewModel.watchStatus) { state ->
bottomPreviewBinding?.resultviewPreviewSubscribe?.apply { setWatchStatus(state)
if (state != null) {
val drawable = if (state) {
R.drawable.ic_baseline_notifications_active_24
} else {
R.drawable.baseline_notifications_none_24
} }
setImageResource(drawable) observe(syncViewModel.userData) { status ->
setUserData(status)
} }
isVisible = state != null
setOnClickListener {
viewModel.toggleSubscriptionStatus(context) { newStatus: Boolean? ->
if (newStatus == null) return@toggleSubscriptionStatus
val message = if (newStatus) {
// Kinda icky to have this here, but it works.
SubscriptionWorkManager.enqueuePeriodicWork(context)
R.string.subscription_new
} else {
R.string.subscription_deleted
}
val name = (viewModel.page.value as? Resource.Success)?.value?.title
?: txt(R.string.no_data).asStringNull(context) ?: ""
showToast(txt(message, name), Toast.LENGTH_SHORT)
}
}
}
}
observe(viewModel.watchStatus, ::setWatchStatus)
observe(syncViewModel.userData, ::setUserData)
observeNullable(viewModel.subscribeStatus, ::setSubscribeStatus)
observeNullable(viewModel.page) { resource -> observeNullable(viewModel.page) { resource ->
if (resource == null) { if (resource == null) {
@ -1433,14 +1386,13 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
resultviewPreviewMetaDuration.setText(d.durationText) resultviewPreviewMetaDuration.setText(d.durationText)
resultviewPreviewMetaRating.setText(d.ratingText) resultviewPreviewMetaRating.setText(d.ratingText)
resultviewPreviewDescription.setTextHtml(d.plotText) resultviewPreviewDescription.setText(d.plotText)
resultviewPreviewPoster.setImage( resultviewPreviewPoster.setImage(
d.posterImage ?: d.posterBackgroundImage d.posterImage ?: d.posterBackgroundImage
) )
setUserData(syncViewModel.userData.value) setUserData(syncViewModel.userData.value)
setWatchStatus(viewModel.watchStatus.value) setWatchStatus(viewModel.watchStatus.value)
setSubscribeStatus(viewModel.subscribeStatus.value)
resultviewPreviewBookmark.setOnClickListener { resultviewPreviewBookmark.setOnClickListener {
//viewModel.updateWatchStatus(WatchType.PLANTOWATCH) //viewModel.updateWatchStatus(WatchType.PLANTOWATCH)
@ -1448,28 +1400,26 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
val value = viewModel.watchStatus.value ?: WatchType.NONE val value = viewModel.watchStatus.value ?: WatchType.NONE
this@MainActivity.showBottomDialog( this@MainActivity.showBottomDialog(
WatchType.entries.map { getString(it.stringRes) }.toList(), WatchType.values().map { getString(it.stringRes) }.toList(),
value.ordinal, value.ordinal,
this@MainActivity.getString(R.string.action_add_to_bookmarks), this@MainActivity.getString(R.string.action_add_to_bookmarks),
showApply = false, showApply = false,
{}) { {}) {
viewModel.updateWatchStatus( viewModel.updateWatchStatus(
WatchType.entries[it], WatchType.values()[it],
this@MainActivity this@MainActivity
) )
} }
} else { } else {
val value = val value = (syncViewModel.userData.value as? Resource.Success)?.value?.status ?: SyncWatchType.NONE
(syncViewModel.userData.value as? Resource.Success)?.value?.status
?: SyncWatchType.NONE
this@MainActivity.showBottomDialog( this@MainActivity.showBottomDialog(
SyncWatchType.entries.map { getString(it.stringRes) }.toList(), SyncWatchType.values().map { getString(it.stringRes) }.toList(),
value.ordinal, value.ordinal,
this@MainActivity.getString(R.string.action_add_to_bookmarks), this@MainActivity.getString(R.string.action_add_to_bookmarks),
showApply = false, showApply = false,
{}) { {}) {
syncViewModel.setStatus(SyncWatchType.entries[it].internalId) syncViewModel.setStatus(SyncWatchType.values()[it].internalId)
syncViewModel.publishUserData() syncViewModel.publishUserData()
} }
} }
@ -1504,7 +1454,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
} }
} }
if (isLayout(PHONE)) // dont want this clickable on tv layout if (!isTvSettings()) // dont want this clickable on tv layout
resultviewPreviewDescription.setOnClickListener { view -> resultviewPreviewDescription.setOnClickListener { view ->
view.context?.let { ctx -> view.context?.let { ctx ->
val builder: AlertDialog.Builder = val builder: AlertDialog.Builder =
@ -1550,26 +1500,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
logError(e) 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) SearchResultBuilder.updateCache(this)
@ -1599,14 +1529,9 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
} }
} }
if (isLayout(TV or EMULATOR)) { if (isTvSettings()) {
if (navDestination.matchDestination(R.id.navigation_home)) { if (navDestination.matchDestination(R.id.navigation_home)) {
attachBackPressedCallback { attachBackPressedCallback()
showConfirmExitDialog()
window?.navigationBarColor =
colorFromAttribute(R.attr.primaryGrayBackground)
updateLocale()
}
} else detachBackPressedCallback() } else detachBackPressedCallback()
} }
} }
@ -1640,7 +1565,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
itemRippleColor = rippleColor itemRippleColor = rippleColor
itemActiveIndicatorColor = rippleColor itemActiveIndicatorColor = rippleColor
setupWithNavController(navController) setupWithNavController(navController)
if (isLayout(TV or EMULATOR)) { if (isTvSettings()) {
background?.alpha = 200 background?.alpha = 200
} else { } else {
background?.alpha = 255 background?.alpha = 255
@ -1774,8 +1699,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
runAutoUpdate() runAutoUpdate()
} }
FcastManager().init(this, false)
APIRepository.dubStatusActive = getApiDubstatusSettings() APIRepository.dubStatusActive = getApiDubstatusSettings()
try { try {
@ -1812,6 +1735,8 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
} }
} catch (e: Exception) { } catch (e: Exception) {
logError(e) logError(e)
} finally {
setKey(HAS_DONE_SETUP_KEY, true)
} }
// Used to check current focus for TV // Used to check current focus for TV
@ -1847,8 +1772,26 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
binding?.navHostFragment?.isInvisible = false binding?.navHostFragment?.isInvisible = false
} }
override fun onAuthenticationError() { private var backPressedCallback: OnBackPressedCallback? = null
finish()
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 { suspend fun checkGithubConnectivity(): Boolean {

View file

@ -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()
}*/
}

View file

@ -1,6 +1,6 @@
package com.lagradost.cloudstream3.extractors package com.lagradost.cloudstream3.extractors
import com.lagradost.api.Log import android.util.Log
import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink

View file

@ -2,11 +2,12 @@ package com.lagradost.cloudstream3.extractors
import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.extractors.helper.*
import com.lagradost.cloudstream3.extractors.helper.AesHelper.cryptoAESHandler 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.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.M3u8Helper import com.lagradost.cloudstream3.utils.M3u8Helper
import kotlin.run
class Moviesapi : Chillx() { class Moviesapi : Chillx() {
override val name = "Moviesapi" override val name = "Moviesapi"
@ -27,44 +28,30 @@ open class Chillx : ExtractorApi() {
override val name = "Chillx" override val name = "Chillx"
override val mainUrl = "https://chillx.top" override val mainUrl = "https://chillx.top"
override val requiresReferer = true override val requiresReferer = true
companion object {
private val keySource = "https://rowdy-avocado.github.io/multi-keys/"
private var key: String? = null private var key: String? = null
private suspend fun fetchKey(): String {
return key
?: run {
val res =
app.get(keySource).parsedSafe<KeysData>()
?: throw ErrorLoadingException("Unable to get keys")
key = res.keys.get(0)
res.keys.get(0)
}
}
private data class KeysData(@JsonProperty("chillx") val keys: List<String>)
}
@Suppress("NAME_SHADOWING")
override suspend fun getUrl( override suspend fun getUrl(
url: String, url: String,
referer: String?, referer: String?,
subtitleCallback: (SubtitleFile) -> Unit, subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit callback: (ExtractorLink) -> Unit
) { ) {
val master = Regex("""JScript[\w+]?\s*=\s*'([^']+)""").find( val master = Regex("\\s*=\\s*'([^']+)").find(
app.get( app.get(
url, 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 ).text
)?.groupValues?.get(1) )?.groupValues?.get(1)
val key = fetchKey() val decrypt = cryptoAESHandler(master ?: return, getKey().toByteArray(), false)?.replace("\\", "") ?: throw ErrorLoadingException("failed to decrypt")
val decrypt = cryptoAESHandler(master ?: "", key.toByteArray(), false)?.replace("\\", "") ?: throw ErrorLoadingException("failed to decrypt")
val source = Regex(""""?file"?:\s*"([^"]+)""").find(decrypt)?.groupValues?.get(1) val source = Regex(""""?file"?:\s*"([^"]+)""").find(decrypt)?.groupValues?.get(1)
val subtitles = Regex("""subtitle"?:\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 matches = subtitlePattern.findAll(subtitles ?: "")
val languageUrlPairs = matches.map { matchResult -> val languageUrlPairs = matches.map { matchResult ->
val (language, url) = matchResult.destructured val (language, url) = matchResult.destructured
@ -103,4 +90,16 @@ open class Chillx : ExtractorApi() {
it.groupValues[1].toInt(16).toChar().toString() 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,
)
} }

View file

@ -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<String>()
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
)
)
}
}
}

View file

@ -9,16 +9,10 @@ import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8 import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8
import java.net.URL import java.net.URL
class Geodailymotion : Dailymotion() {
override val name = "GeoDailymotion"
override val mainUrl = "https://geo.dailymotion.com"
}
open class Dailymotion : ExtractorApi() { open class Dailymotion : ExtractorApi() {
override val mainUrl = "https://www.dailymotion.com" override val mainUrl = "https://www.dailymotion.com"
override val name = "Dailymotion" override val name = "Dailymotion"
override val requiresReferer = false override val requiresReferer = false
private val baseUrl = "https://www.dailymotion.com"
@Suppress("RegExpSimplifiable") @Suppress("RegExpSimplifiable")
private val videoIdRegex = "^[kx][a-zA-Z0-9]+\$".toRegex() private val videoIdRegex = "^[kx][a-zA-Z0-9]+\$".toRegex()
@ -40,7 +34,7 @@ open class Dailymotion : ExtractorApi() {
val dmV1st = config.dmInternalData.v1st val dmV1st = config.dmInternalData.v1st
val dmTs = config.dmInternalData.ts val dmTs = config.dmInternalData.ts
val embedder = config.context.embedder 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) val metaData = app.get(metaDataUrl, referer = embedUrl, cookies = req.cookies)
.parsedSafe<MetaData>() ?: return .parsedSafe<MetaData>() ?: return
metaData.qualities.forEach { (_, video) -> metaData.qualities.forEach { (_, video) ->
@ -51,19 +45,16 @@ open class Dailymotion : ExtractorApi() {
} }
private fun getEmbedUrl(url: String): String? { private fun getEmbedUrl(url: String): String? {
if (url.contains("/embed/") || url.contains("/video/")) { if (url.contains("/embed/")) {
return url return url
} }
if (url.contains("geo.dailymotion.com")) { val vid = getVideoId(url) ?: return null
val videoId = url.substringAfter("video=") return "$mainUrl/embed/video/$vid"
return "$baseUrl/embed/video/$videoId"
}
return null
} }
private fun getVideoId(url: String): String? { private fun getVideoId(url: String): String? {
val path = URL(url).path val path = URL(url).path
val id = path.substringAfter("/video/") val id = path.substringAfter("video/")
if (id.matches(videoIdRegex)) { if (id.matches(videoIdRegex)) {
return id return id
} }

View file

@ -7,18 +7,6 @@ import com.lagradost.cloudstream3.utils.Qualities
import com.lagradost.cloudstream3.utils.getQualityFromName import com.lagradost.cloudstream3.utils.getQualityFromName
import kotlinx.coroutines.delay 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() { class Dooood : DoodLaExtractor() {
override var mainUrl = "https://dooood.com" override var mainUrl = "https://dooood.com"
} }
@ -68,10 +56,9 @@ open class DoodLaExtractor : ExtractorApi() {
} }
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? { override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
val newUrl= url.replace(mainUrl, "https://d0000d.com") val response0 = app.get(url).text // html of DoodStream page to look for /pass_md5/...
val response0 = app.get(newUrl).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 md5 ="https://d0000d.com"+(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 trueUrl = app.get(md5, referer = newUrl).text + "zUEJeL3mUN?token=" + md5.substringAfterLast("/") //direct link to extract (zUEJeL3mUN is random)
val quality = Regex("\\d{3,4}p").find(response0.substringAfter("<title>").substringBefore("</title>"))?.groupValues?.get(0) val quality = Regex("\\d{3,4}p").find(response0.substringAfter("<title>").substringBefore("</title>"))?.groupValues?.get(0)
return listOf( return listOf(
ExtractorLink( ExtractorLink(

View file

@ -2,7 +2,7 @@
package com.lagradost.cloudstream3.extractors package com.lagradost.cloudstream3.extractors
import com.lagradost.api.Log import android.util.Log
import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.extractors.helper.AesHelper import com.lagradost.cloudstream3.extractors.helper.AesHelper
@ -16,23 +16,24 @@ open class HDMomPlayer : ExtractorApi() {
override val requiresReferer = true override val requiresReferer = true
override suspend fun getUrl(url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit) { override suspend fun getUrl(url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit) {
val m3uLink:String? val m3u_link:String?
val extRef = referer ?: "" val ext_ref = referer ?: ""
val iSource = app.get(url, referer=extRef).text 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) { if (bePlayer != null) {
val bePlayerPass = bePlayer.get(1) val bePlayerPass = bePlayer.get(1)
val bePlayerData = bePlayer.get(2) val bePlayerData = bePlayer.get(2)
val encrypted = AesHelper.cryptoAESHandler(bePlayerData, bePlayerPass.toByteArray(), false)?.replace("\\", "") ?: throw ErrorLoadingException("failed to decrypt") 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 { } 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) val track_str = Regex("""tracks:\[([^\]]+)""").find(i_source)?.groupValues?.get(1)
if (trackStr != null) { if (track_str != null) {
val tracks:List<Track> = jacksonObjectMapper().readValue("[${trackStr}]") val tracks:List<Track> = jacksonObjectMapper().readValue("[${track_str}]")
for (track in tracks) { for (track in tracks) {
if (track.file == null || track.label == null) continue if (track.file == null || track.label == null) continue
@ -52,7 +53,7 @@ open class HDMomPlayer : ExtractorApi() {
ExtractorLink( ExtractorLink(
source = this.name, source = this.name,
name = this.name, name = this.name,
url = m3uLink ?: throw ErrorLoadingException("m3u link not found"), url = m3u_link ?: throw ErrorLoadingException("m3u link not found"),
referer = url, referer = url,
quality = Qualities.Unknown.value, quality = Qualities.Unknown.value,
isM3u8 = true isM3u8 = true

View file

@ -2,7 +2,7 @@
package com.lagradost.cloudstream3.extractors package com.lagradost.cloudstream3.extractors
import com.lagradost.api.Log import android.util.Log
import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.*
import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonProperty
@ -13,36 +13,37 @@ open class HDPlayerSystem : ExtractorApi() {
override val requiresReferer = true override val requiresReferer = true
override suspend fun getUrl(url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit) { override suspend fun getUrl(url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit) {
val extRef = referer ?: "" val ext_ref = referer ?: ""
val vidId = if (url.contains("video/")) { val vid_id = if (url.contains("video/")) {
url.substringAfter("video/") url.substringAfter("video/")
} else { } else {
url.substringAfter("?data=") url.substringAfter("?data=")
} }
val postUrl = "${mainUrl}/player/index.php?data=${vidId}&do=getVideo" val post_url = "${mainUrl}/player/index.php?data=${vid_id}&do=getVideo"
Log.d("Kekik_${this.name}", "post_url » ${post_url}")
val response = app.post( val response = app.post(
postUrl, post_url,
data = mapOf( data = mapOf(
"hash" to vidId, "hash" to vid_id,
"r" to extRef "r" to ext_ref
), ),
referer = extRef, referer = ext_ref,
headers = mapOf( headers = mapOf(
"Content-Type" to "application/x-www-form-urlencoded; charset=UTF-8", "Content-Type" to "application/x-www-form-urlencoded; charset=UTF-8",
"X-Requested-With" to "XMLHttpRequest" "X-Requested-With" to "XMLHttpRequest"
) )
) )
val videoResponse = response.parsedSafe<SystemResponse>() ?: throw ErrorLoadingException("failed to parse response") val video_response = response.parsedSafe<SystemResponse>() ?: throw ErrorLoadingException("failed to parse response")
val m3uLink = videoResponse.securedLink val m3u_link = video_response.securedLink
callback.invoke( callback.invoke(
ExtractorLink( ExtractorLink(
source = this.name, source = this.name,
name = this.name, name = this.name,
url = m3uLink, url = m3u_link,
referer = extRef, referer = ext_ref,
quality = Qualities.Unknown.value, quality = Qualities.Unknown.value,
type = INFER_TYPE type = INFER_TYPE
) )

View file

@ -21,13 +21,3 @@ class FourPlayRu : ContentX() {
override var name = "FourPlayRu" override var name = "FourPlayRu"
override var mainUrl = "https://four.playru.net" override var mainUrl = "https://four.playru.net"
} }
class Pichive : ContentX() {
override var name = "Pichive"
override var mainUrl = "https://pichive.online"
}
class FourPichive : ContentX() {
override var name = "FourPichive"
override var mainUrl = "https://four.pichive.online"
}

View file

@ -2,7 +2,7 @@
package com.lagradost.cloudstream3.extractors package com.lagradost.cloudstream3.extractors
import com.lagradost.api.Log import android.util.Log
import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.*
import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonProperty
@ -13,25 +13,28 @@ open class MailRu : ExtractorApi() {
override val requiresReferer = false override val requiresReferer = false
override suspend fun getUrl(url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit) { override suspend fun getUrl(url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit) {
val extRef = referer ?: "" val ext_ref = referer ?: ""
Log.d("Kekik_${this.name}", "url » ${url}")
val vidId = url.substringAfter("video/embed/").trim() val vid_id = url.substringAfter("video/embed/").trim()
val videoReq = app.get("${mainUrl}/+/video/meta/${vidId}", referer=url) val video_req = app.get("${mainUrl}/+/video/meta/${vid_id}", referer=url)
val videoKey = videoReq.cookies["video_key"].toString() val video_key = video_req.cookies["video_key"].toString()
Log.d("Kekik_${this.name}", "video_key » ${video_key}")
val videoData = AppUtils.tryParseJson<MailRuData>(videoReq.text) ?: throw ErrorLoadingException("Video not found") val video_data = AppUtils.tryParseJson<MailRuData>(video_req.text) ?: throw ErrorLoadingException("Video not found")
for (video in videoData.videos) { for (video in video_data.videos) {
Log.d("Kekik_${this.name}", "video » ${video}")
val videoUrl = if (video.url.startsWith("//")) "https:${video.url}" else video.url val video_url = if (video.url.startsWith("//")) "https:${video.url}" else video.url
callback.invoke( callback.invoke(
ExtractorLink( ExtractorLink(
source = this.name, source = this.name,
name = this.name, name = this.name,
url = videoUrl, url = video_url,
referer = url, referer = url,
headers = mapOf("Cookie" to "video_key=${videoKey}"), headers = mapOf("Cookie" to "video_key=${video_key}"),
quality = getQualityFromName(video.key), quality = getQualityFromName(video.key),
isM3u8 = false isM3u8 = false
) )

View file

@ -2,7 +2,7 @@
package com.lagradost.cloudstream3.extractors package com.lagradost.cloudstream3.extractors
import com.lagradost.api.Log import android.util.Log
import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.*
import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonProperty
@ -13,20 +13,22 @@ open class Odnoklassniki : ExtractorApi() {
override val requiresReferer = false override val requiresReferer = false
override suspend fun getUrl(url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit) { override suspend fun getUrl(url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit) {
val extRef = referer ?: "" val ext_ref = referer ?: ""
Log.d("Kekik_${this.name}", "url » ${url}")
val userAgent = mapOf("User-Agent" to "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36") val user_agent = mapOf("User-Agent" to "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36")
val videoReq = app.get(url, headers=userAgent).text.replace("\\&quot;", "\"").replace("\\\\", "\\") val video_req = app.get(url, headers=user_agent).text.replace("\\&quot;", "\"").replace("\\\\", "\\")
.replace(Regex("\\\\u([0-9A-Fa-f]{4})")) { matchResult -> .replace(Regex("\\\\u([0-9A-Fa-f]{4})")) { matchResult ->
Integer.parseInt(matchResult.groupValues[1], 16).toChar().toString() Integer.parseInt(matchResult.groupValues[1], 16).toChar().toString()
} }
val videosStr = Regex("""\"videos\":(\[[^\]]*\])""").find(videoReq)?.groupValues?.get(1) ?: throw ErrorLoadingException("Video not found") val videos_str = Regex("""\"videos\":(\[[^\]]*\])""").find(video_req)?.groupValues?.get(1) ?: throw ErrorLoadingException("Video not found")
val videos = AppUtils.tryParseJson<List<OkRuVideo>>(videosStr) ?: throw ErrorLoadingException("Video not found") val videos = AppUtils.tryParseJson<List<OkRuVideo>>(videos_str) ?: throw ErrorLoadingException("Video not found")
for (video in videos) { for (video in videos) {
Log.d("Kekik_${this.name}", "video » ${video}")
val videoUrl = if (video.url.startsWith("//")) "https:${video.url}" else video.url val video_url = if (video.url.startsWith("//")) "https:${video.url}" else video.url
val quality = video.name.uppercase() val quality = video.name.uppercase()
.replace("MOBILE", "144p") .replace("MOBILE", "144p")
@ -42,10 +44,10 @@ open class Odnoklassniki : ExtractorApi() {
ExtractorLink( ExtractorLink(
source = this.name, source = this.name,
name = this.name, name = this.name,
url = videoUrl, url = video_url,
referer = url, referer = url,
quality = getQualityFromName(quality), quality = getQualityFromName(quality),
headers = userAgent, headers = user_agent,
isM3u8 = false isM3u8 = false
) )
) )

View file

@ -2,7 +2,7 @@
package com.lagradost.cloudstream3.extractors package com.lagradost.cloudstream3.extractors
import com.lagradost.api.Log import android.util.Log
import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.*
import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonProperty
@ -13,38 +13,39 @@ open class PeaceMakerst : ExtractorApi() {
override val requiresReferer = true override val requiresReferer = true
override suspend fun getUrl(url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit) { override suspend fun getUrl(url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit) {
val m3uLink:String? val m3u_link:String?
val extRef = referer ?: "" val ext_ref = referer ?: ""
val postUrl = "${url}?do=getVideo" val post_url = "${url}?do=getVideo"
Log.d("Kekik_${this.name}", "post_url » ${post_url}")
val response = app.post( val response = app.post(
postUrl, post_url,
data = mapOf( data = mapOf(
"hash" to url.substringAfter("video/"), "hash" to url.substringAfter("video/"),
"r" to extRef, "r" to ext_ref,
"s" to "" "s" to ""
), ),
referer = extRef, referer = ext_ref,
headers = mapOf( headers = mapOf(
"Content-Type" to "application/x-www-form-urlencoded; charset=UTF-8", "Content-Type" to "application/x-www-form-urlencoded; charset=UTF-8",
"X-Requested-With" to "XMLHttpRequest" "X-Requested-With" to "XMLHttpRequest"
) )
) )
if (response.text.contains("teve2.com.tr\\/embed\\/")) { if (response.text.contains("teve2.com.tr\\/embed\\/")) {
val teve2Id = response.text.substringAfter("teve2.com.tr\\/embed\\/").substringBefore("\"") val teve2_id = response.text.substringAfter("teve2.com.tr\\/embed\\/").substringBefore("\"")
val teve2Response = app.get( val teve2_response = app.get(
"https://www.teve2.com.tr/action/media/${teve2Id}", "https://www.teve2.com.tr/action/media/${teve2_id}",
referer = "https://www.teve2.com.tr/embed/${teve2Id}" referer = "https://www.teve2.com.tr/embed/${teve2_id}"
).parsedSafe<Teve2ApiResponse>() ?: throw ErrorLoadingException("teve2 response is null") ).parsedSafe<Teve2ApiResponse>() ?: throw ErrorLoadingException("teve2 response is null")
m3uLink = teve2Response.media.link.serviceUrl + "//" + teve2Response.media.link.securePath m3u_link = teve2_response.media.link.serviceUrl + "//" + teve2_response.media.link.securePath
} else { } else {
val videoResponse = response.parsedSafe<PeaceResponse>() ?: throw ErrorLoadingException("peace response is null") val video_response = response.parsedSafe<PeaceResponse>() ?: throw ErrorLoadingException("peace response is null")
val videoSources = videoResponse.videoSources val video_sources = video_response.videoSources
if (videoSources.isNotEmpty()) { if (video_sources.isNotEmpty()) {
m3uLink = videoSources.lastOrNull()?.file m3u_link = video_sources.lastOrNull()?.file
} else { } else {
m3uLink = null m3u_link = null
} }
} }
@ -52,8 +53,8 @@ open class PeaceMakerst : ExtractorApi() {
ExtractorLink( ExtractorLink(
source = this.name, source = this.name,
name = this.name, name = this.name,
url = m3uLink ?: throw ErrorLoadingException("m3u link not found"), url = m3u_link ?: throw ErrorLoadingException("m3u link not found"),
referer = extRef, referer = ext_ref,
quality = Qualities.Unknown.value, quality = Qualities.Unknown.value,
type = INFER_TYPE type = INFER_TYPE
) )

View file

@ -1,6 +1,6 @@
package com.lagradost.cloudstream3.extractors package com.lagradost.cloudstream3.extractors
import com.lagradost.api.Log import android.util.Log
import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.*

View file

@ -0,0 +1,50 @@
// ! 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 RapidVid : ExtractorApi() {
override val name = "RapidVid"
override val mainUrl = "https://rapidvid.net"
override val requiresReferer = true
override suspend fun getUrl(url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit) {
val ext_ref = referer ?: ""
val video_req = app.get(url, referer=ext_ref).text
val sub_urls = mutableSetOf<String>()
Regex("""captions\",\"file\":\"([^\"]+)\",\"label\":\"([^\"]+)\"""").findAll(video_req).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 extracted_value = Regex("""file": "(.*)",""").find(video_req)?.groupValues?.get(1) ?: throw ErrorLoadingException("File not found")
val bytes = extracted_value.split("\\x").filter { it.isNotEmpty() }.map { it.toInt(16).toByte() }.toByteArray()
val decoded = String(bytes, Charsets.UTF_8)
Log.d("Kekik_${this.name}", "decoded » ${decoded}")
callback.invoke(
ExtractorLink(
source = this.name,
name = this.name,
url = decoded,
referer = ext_ref,
quality = Qualities.Unknown.value,
isM3u8 = true
)
)
}
}

View file

@ -2,7 +2,7 @@
package com.lagradost.cloudstream3.extractors package com.lagradost.cloudstream3.extractors
import com.lagradost.api.Log import android.util.Log
import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.*
@ -12,17 +12,18 @@ open class SibNet : ExtractorApi() {
override val requiresReferer = true override val requiresReferer = true
override suspend fun getUrl(url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit) { override suspend fun getUrl(url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit) {
val extRef = referer ?: "" val ext_ref = referer ?: ""
val iSource = app.get(url, referer=extRef).text val i_source = app.get(url, referer=ext_ref).text
var m3uLink = Regex("""player.src\(\[\{src: \"([^\"]+)""").find(iSource)?.groupValues?.get(1) ?: throw ErrorLoadingException("m3u link not found") var m3u_link = Regex("""player.src\(\[\{src: \"([^\"]+)""").find(i_source)?.groupValues?.get(1) ?: throw ErrorLoadingException("m3u link not found")
m3uLink = "${mainUrl}${m3uLink}" m3u_link = "${mainUrl}${m3u_link}"
Log.d("Kekik_${this.name}", "m3u_link » ${m3u_link}")
callback.invoke( callback.invoke(
ExtractorLink( ExtractorLink(
source = this.name, source = this.name,
name = this.name, name = this.name,
url = m3uLink, url = m3u_link,
referer = url, referer = url,
quality = Qualities.Unknown.value, quality = Qualities.Unknown.value,
type = INFER_TYPE type = INFER_TYPE

View file

@ -0,0 +1,34 @@
package com.lagradost.cloudstream3.extractors
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.network.WebViewResolver
import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.Qualities
open class StreamWishExtractor : ExtractorApi() {
override var name = "StreamWish"
override var mainUrl = "https://streamwish.to"
override val requiresReferer = false
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
val response = app.get(
url, referer = referer ?: "$mainUrl/", interceptor = WebViewResolver(
Regex("""master\.m3u8""")
)
)
val sources = mutableListOf<ExtractorLink>()
if (response.url.contains("m3u8"))
sources.add(
ExtractorLink(
source = name,
name = name,
url = response.url,
referer = referer ?: "$mainUrl/",
quality = Qualities.Unknown.value,
isM3u8 = true
)
)
return sources
}
}

View file

@ -13,7 +13,7 @@ data class Files(
open class Supervideo : ExtractorApi() { open class Supervideo : ExtractorApi() {
override var name = "Supervideo" override var name = "Supervideo"
override var mainUrl = "https://supervideo.cc" override var mainUrl = "https://supervideo.tv"
override val requiresReferer = false override val requiresReferer = false
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? { override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
val extractedLinksList: MutableList<ExtractorLink> = mutableListOf() val extractedLinksList: MutableList<ExtractorLink> = mutableListOf()

View file

@ -2,7 +2,7 @@
package com.lagradost.cloudstream3.extractors package com.lagradost.cloudstream3.extractors
import com.lagradost.api.Log import android.util.Log
import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.*
import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonProperty
@ -13,13 +13,13 @@ open class TRsTX : ExtractorApi() {
override val requiresReferer = true override val requiresReferer = true
override suspend fun getUrl(url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit) { override suspend fun getUrl(url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit) {
val extRef = referer ?: "" val ext_ref = referer ?: ""
val videoReq = app.get(url, referer=extRef).text val video_req = app.get(url, referer=ext_ref).text
val file = Regex("""file\":\"([^\"]+)""").find(videoReq)?.groupValues?.get(1) ?: throw ErrorLoadingException("File not found") val file = Regex("""file\":\"([^\"]+)""").find(video_req)?.groupValues?.get(1) ?: throw ErrorLoadingException("File not found")
val postLink = "${mainUrl}/" + file.replace("\\", "") val postLink = "${mainUrl}/" + file.replace("\\", "")
val rawList = app.post(postLink, referer=extRef).parsedSafe<List<Any>>() ?: throw ErrorLoadingException("Post link not found") val rawList = app.post(postLink, referer=ext_ref).parsedSafe<List<Any>>() ?: throw ErrorLoadingException("Post link not found")
val postJson: List<TrstxVideoData> = rawList.drop(1).map { item -> val postJson: List<TrstxVideoData> = rawList.drop(1).map { item ->
val mapItem = item as Map<*, *> val mapItem = item as Map<*, *>
@ -28,35 +28,37 @@ open class TRsTX : ExtractorApi() {
file = mapItem["file"] as? String file = mapItem["file"] as? String
) )
} }
Log.d("Kekik_${this.name}", "postJson » ${postJson}")
val vidLinks = mutableSetOf<String>() val vid_links = mutableSetOf<String>()
val vidMap = mutableListOf<Map<String, String>>() val vid_map = mutableListOf<Map<String, String>>()
for (item in postJson) { for (item in postJson) {
if (item.file == null || item.title == null) continue if (item.file == null || item.title == null) continue
val fileUrl = "${mainUrl}/playlist/" + item.file.substring(1) + ".txt" val fileUrl = "${mainUrl}/playlist/" + item.file.substring(1) + ".txt"
val videoData = app.post(fileUrl, referer=extRef).text val videoData = app.post(fileUrl, referer=ext_ref).text
if (videoData in vidLinks) { continue } if (videoData in vid_links) { continue }
vidLinks.add(videoData) vid_links.add(videoData)
vidMap.add(mapOf( vid_map.add(mapOf(
"title" to item.title, "title" to item.title,
"videoData" to videoData "videoData" to videoData
)) ))
} }
for (mapEntry in vidMap) { for (mapEntry in vid_map) {
Log.d("Kekik_${this.name}", "mapEntry » ${mapEntry}")
val title = mapEntry["title"] ?: continue val title = mapEntry["title"] ?: continue
val m3uLink = mapEntry["videoData"] ?: continue val m3u_link = mapEntry["videoData"] ?: continue
callback.invoke( callback.invoke(
ExtractorLink( ExtractorLink(
source = this.name, source = this.name,
name = "${this.name} - ${title}", name = "${this.name} - ${title}",
url = m3uLink, url = m3u_link,
referer = extRef, referer = ext_ref,
quality = Qualities.Unknown.value, quality = Qualities.Unknown.value,
type = INFER_TYPE type = INFER_TYPE
) )

View file

@ -2,7 +2,7 @@
package com.lagradost.cloudstream3.extractors package com.lagradost.cloudstream3.extractors
import com.lagradost.api.Log import android.util.Log
import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.*
import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonProperty
@ -13,11 +13,12 @@ open class TauVideo : ExtractorApi() {
override val requiresReferer = true override val requiresReferer = true
override suspend fun getUrl(url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit) { override suspend fun getUrl(url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit) {
val extRef = referer ?: "" val ext_ref = referer ?: ""
val videoKey = url.split("/").last() val video_key = url.split("/").last()
val videoUrl = "${mainUrl}/api/video/${videoKey}" val video_url = "${mainUrl}/api/video/${video_key}"
Log.d("Kekik_${this.name}", "video_url » ${video_url}")
val api = app.get(videoUrl).parsedSafe<TauVideoUrls>() ?: throw ErrorLoadingException("TauVideo") val api = app.get(video_url).parsedSafe<TauVideoUrls>() ?: throw ErrorLoadingException("TauVideo")
for (video in api.urls) { for (video in api.urls) {
callback.invoke( callback.invoke(
@ -25,7 +26,7 @@ open class TauVideo : ExtractorApi() {
source = this.name, source = this.name,
name = this.name, name = this.name,
url = video.url, url = video.url,
referer = extRef, referer = ext_ref,
quality = getQualityFromName(video.label), quality = getQualityFromName(video.label),
type = INFER_TYPE type = INFER_TYPE
) )

View file

@ -0,0 +1,50 @@
// ! 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 VidMoxy : ExtractorApi() {
override val name = "VidMoxy"
override val mainUrl = "https://vidmoxy.com"
override val requiresReferer = true
override suspend fun getUrl(url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit) {
val ext_ref = referer ?: ""
val video_req = app.get(url, referer=ext_ref).text
val sub_urls = mutableSetOf<String>()
Regex("""captions\",\"file\":\"([^\"]+)\",\"label\":\"([^\"]+)\"""").findAll(video_req).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 extracted_value = Regex("""file": "(.*)",""").find(video_req)?.groupValues?.get(1) ?: throw ErrorLoadingException("File not found")
val bytes = extracted_value.split("\\x").filter { it.isNotEmpty() }.map { it.toInt(16).toByte() }.toByteArray()
val decoded = String(bytes, Charsets.UTF_8)
Log.d("Kekik_${this.name}", "decoded » ${decoded}")
callback.invoke(
ExtractorLink(
source = this.name,
name = this.name,
url = decoded,
referer = ext_ref,
quality = Qualities.Unknown.value,
isM3u8 = true
)
)
}
}

View file

@ -0,0 +1,100 @@
package com.lagradost.cloudstream3.extractors
import com.lagradost.cloudstream3.SubtitleFile
import com.lagradost.cloudstream3.amap
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.*
import kotlinx.coroutines.delay
import java.net.URI
class VidSrcExtractor2 : VidSrcExtractor() {
override val mainUrl = "https://vidsrc.me/embed"
override suspend fun getUrl(
url: String,
referer: String?,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
) {
val newUrl = url.lowercase().replace(mainUrl, super.mainUrl)
super.getUrl(newUrl, referer, subtitleCallback, callback)
}
}
open class VidSrcExtractor : ExtractorApi() {
override val name = "VidSrc"
private val absoluteUrl = "https://v2.vidsrc.me"
override val mainUrl = "$absoluteUrl/embed"
override val requiresReferer = false
companion object {
/** Infinite function to validate the vidSrc pass */
suspend fun validatePass(url: String) {
val uri = URI(url)
val host = uri.host
// Basically turn https://tm3p.vidsrc.stream/ -> https://vidsrc.stream/
val referer = host.split(".").let {
val size = it.size
"https://" + it.subList(maxOf(0, size - 2), size).joinToString(".") + "/"
}
while (true) {
app.get(url, referer = referer)
delay(60_000)
}
}
}
override suspend fun getUrl(
url: String,
referer: String?,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
) {
val iframedoc = app.get(url).document
val serverslist =
iframedoc.select("div#sources.button_content div#content div#list div").map {
val datahash = it.attr("data-hash")
if (datahash.isNotBlank()) {
val links = try {
app.get(
"$absoluteUrl/srcrcp/$datahash",
referer = "https://rcp.vidsrc.me/"
).url
} catch (e: Exception) {
""
}
links
} else ""
}
serverslist.amap { server ->
val linkfixed = server.replace("https://vidsrc.xyz/", "https://embedsito.com/")
if (linkfixed.contains("/prorcp")) {
val srcresponse = app.get(server, referer = absoluteUrl).text
val m3u8Regex = Regex("((https:|http:)//.*\\.m3u8)")
val srcm3u8 = m3u8Regex.find(srcresponse)?.value ?: return@amap
val passRegex = Regex("""['"](.*set_pass[^"']*)""")
val pass = passRegex.find(srcresponse)?.groupValues?.get(1)?.replace(
Regex("""^//"""), "https://"
)
callback.invoke(
ExtractorLink(
this.name,
this.name,
srcm3u8,
"https://vidsrc.stream/",
Qualities.Unknown.value,
extractorData = pass,
isM3u8 = true
)
)
} else {
loadExtractor(linkfixed, url, subtitleCallback, callback)
}
}
}
}

View file

@ -2,7 +2,7 @@
package com.lagradost.cloudstream3.extractors package com.lagradost.cloudstream3.extractors
import com.lagradost.api.Log import android.util.Log
import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.*
import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonProperty
@ -15,13 +15,14 @@ open class VideoSeyred : ExtractorApi() {
override val requiresReferer = true override val requiresReferer = true
override suspend fun getUrl(url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit) { override suspend fun getUrl(url: String, referer: String?, subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit) {
val extRef = referer ?: "" val ext_ref = referer ?: ""
val videoId = url.substringAfter("embed/").substringBefore("?") val video_id = url.substringAfter("embed/").substringBefore("?")
val videoUrl = "${mainUrl}/playlist/${videoId}.json" val video_url = "${mainUrl}/playlist/${video_id}.json"
Log.d("Kekik_${this.name}", "video_url » ${video_url}")
val responseRaw = app.get(videoUrl) val response_raw = app.get(video_url)
val responseList:List<VideoSeyredSource> = jacksonObjectMapper().readValue(responseRaw.text) ?: throw ErrorLoadingException("VideoSeyred") val response_list:List<VideoSeyredSource> = jacksonObjectMapper().readValue(response_raw.text) ?: throw ErrorLoadingException("VideoSeyred")
val response = responseList[0] ?: throw ErrorLoadingException("VideoSeyred") val response = response_list[0] ?: throw ErrorLoadingException("VideoSeyred")
for (track in response.tracks) { for (track in response.tracks) {
if (track.label != null && track.kind == "captions") { if (track.label != null && track.kind == "captions") {
@ -40,7 +41,7 @@ open class VideoSeyred : ExtractorApi() {
source = this.name, source = this.name,
name = this.name, name = this.name,
url = source.file, url = source.file,
referer = "${mainUrl}/", referer = ext_ref,
quality = Qualities.Unknown.value, quality = Qualities.Unknown.value,
type = INFER_TYPE type = INFER_TYPE
) )

View file

@ -25,13 +25,9 @@ open class Vidmoly : ExtractorApi() {
subtitleCallback: (SubtitleFile) -> Unit, subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit callback: (ExtractorLink) -> Unit
) { ) {
val headers = mapOf(
"User-Agent" to "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36",
"Sec-Fetch-Dest" to "iframe"
)
val script = app.get( val script = app.get(
url, url,
headers = headers,
referer = referer, referer = referer,
).document.select("script") ).document.select("script")
.find { it.data().contains("sources:") }?.data() .find { it.data().contains("sources:") }?.data()

View file

@ -0,0 +1,116 @@
package com.lagradost.cloudstream3.extractors
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.SubtitleFile
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.base64Encode
import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.M3u8Helper
import javax.crypto.Cipher
import javax.crypto.spec.SecretKeySpec
// Code found in https://github.com/KillerDogeEmpire/vidplay-keys
// special credits to @KillerDogeEmpire for providing key
class MyCloud : Vidplay() {
override val name = "MyCloud"
override val mainUrl = "https://mcloud.bz"
}
class VidplayOnline : Vidplay() {
override val mainUrl = "https://vidplay.online"
}
open class Vidplay : ExtractorApi() {
override val name = "Vidplay"
override val mainUrl = "https://vidplay.site"
override val requiresReferer = true
open val key =
"https://raw.githubusercontent.com/KillerDogeEmpire/vidplay-keys/keys/keys.json"
override suspend fun getUrl(
url: String,
referer: String?,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
) {
val id = url.substringBefore("?").substringAfterLast("/")
val encodeId = encodeId(id, getKeys())
val mediaUrl = callFutoken(encodeId, url)
val res = app.get(
"$mediaUrl", headers = mapOf(
"Accept" to "application/json, text/javascript, */*; q=0.01",
"X-Requested-With" to "XMLHttpRequest",
), referer = url
).parsedSafe<Response>()?.result
res?.sources?.map {
M3u8Helper.generateM3u8(
this.name,
it.file ?: return@map,
"$mainUrl/"
).forEach(callback)
}
res?.tracks?.filter { it.kind == "captions" }?.map {
subtitleCallback.invoke(
SubtitleFile(it.label ?: return@map, it.file ?: return@map)
)
}
}
private suspend fun getKeys(): List<String> {
return app.get(key).parsed()
}
private suspend fun callFutoken(id: String, url: String): String? {
val script = app.get("$mainUrl/futoken").text
val k = "k='(\\S+)'".toRegex().find(script)?.groupValues?.get(1) ?: return null
val a = mutableListOf(k)
for (i in id.indices) {
a.add((k[i % k.length].code + id[i].code).toString())
}
return "$mainUrl/mediainfo/${a.joinToString(",")}?${url.substringAfter("?")}"
}
private fun encodeId(id: String, keyList: List<String>): String {
val cipher1 = Cipher.getInstance("RC4")
val cipher2 = Cipher.getInstance("RC4")
cipher1.init(
Cipher.DECRYPT_MODE,
SecretKeySpec(keyList[0].toByteArray(), "RC4"),
cipher1.parameters
)
cipher2.init(
Cipher.DECRYPT_MODE,
SecretKeySpec(keyList[1].toByteArray(), "RC4"),
cipher2.parameters
)
var input = id.toByteArray()
input = cipher1.doFinal(input)
input = cipher2.doFinal(input)
return base64Encode(input).replace("/", "_")
}
data class Tracks(
@JsonProperty("file") val file: String? = null,
@JsonProperty("label") val label: String? = null,
@JsonProperty("kind") val kind: String? = null,
)
data class Sources(
@JsonProperty("file") val file: String? = null,
)
data class Result(
@JsonProperty("sources") val sources: ArrayList<Sources>? = arrayListOf(),
@JsonProperty("tracks") val tracks: ArrayList<Tracks>? = arrayListOf(),
)
data class Response(
@JsonProperty("result") val result: Result? = null,
)
}

View file

@ -0,0 +1,36 @@
package com.lagradost.cloudstream3.extractors
import com.lagradost.cloudstream3.SubtitleFile
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.M3u8Helper
class Tubeless : Voe() {
override var mainUrl = "https://tubelessceliolymph.com"
}
open class Voe : ExtractorApi() {
override val name = "Voe"
override val mainUrl = "https://voe.sx"
override val requiresReferer = true
override suspend fun getUrl(
url: String,
referer: String?,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
) {
val res = app.get(url, referer = referer).document
val script = res.select("script").find { it.data().contains("sources =") }?.data()
val link = Regex("[\"']hls[\"']:\\s*[\"'](.*)[\"']").find(script ?: return)?.groupValues?.get(1)
M3u8Helper.generateM3u8(
name,
link ?: return,
"$mainUrl/",
headers = mapOf("Origin" to "$mainUrl/")
).forEach(callback)
}
}

Some files were not shown because too many files have changed in this diff Show more