Compare commits

..

1 commit

Author SHA1 Message Date
firelight
a83239c252
Revert "chore: refactor gradlelocalproperties and update gradle plugin (#957)"
This reverts commit 358a20eb77.
2024-06-05 23:40:48 +02:00
378 changed files with 4264 additions and 9668 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}")

View file

@ -14,6 +14,7 @@ plugins {
val tmpFilePath = System.getProperty("user.home") + "/work/_temp/keystore/" val tmpFilePath = System.getProperty("user.home") + "/work/_temp/keystore/"
val prereleaseStoreFile: File? = File(tmpFilePath).listFiles()?.first() val prereleaseStoreFile: File? = File(tmpFilePath).listFiles()?.first()
var isLibraryDebug = false
fun String.execute() = ByteArrayOutputStream().use { baot -> fun String.execute() = ByteArrayOutputStream().use { baot ->
if (project.exec { if (project.exec {
@ -42,8 +43,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 +61,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.2"
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() ?: "")
@ -104,6 +105,7 @@ android {
) )
} }
debug { debug {
isLibraryDebug = true
isDebuggable = true isDebuggable = true
applicationIdSuffix = ".debug" applicationIdSuffix = ".debug"
proguardFiles( proguardFiles(
@ -124,11 +126,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()
} }
@ -161,16 +159,16 @@ dependencies {
testImplementation("junit:junit:4.13.2") testImplementation("junit:junit:4.13.2")
testImplementation("org.json:json:20240303") testImplementation("org.json:json:20240303")
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.13.1")
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.7")
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.7")
// Design & UI // Design & UI
@ -186,9 +184,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:33.2.0-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 +202,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:fafd471") /* 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 +217,14 @@ 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.15") // run JavaScript
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. */
@ -238,14 +235,7 @@ dependencies {
implementation("com.github.Blatzar:NiceHttp:0.4.11") // HTTP Lib implementation("com.github.Blatzar:NiceHttp:0.4.11") // HTTP Lib
implementation(project(":library") { implementation(project(":library") {
// There does not seem to be a good way of getting the android flavor. this.extra.set("isDebug", isLibraryDebug)
val isDebug = gradle.startParameter.taskRequests.any { task ->
task.args.any { arg ->
arg.contains("debug", true)
}
}
this.extra.set("isDebug", isDebug)
}) })
} }
@ -267,8 +257,6 @@ tasks.register<Copy>("copyJar") {
// Merge the app classes and the library classes into classes.jar // Merge the app classes and the library classes into classes.jar
tasks.register<Jar>("makeJar") { tasks.register<Jar>("makeJar") {
// Duplicates cause hard to catch errors, better to fail at compile time.
duplicatesStrategy = DuplicatesStrategy.FAIL
dependsOn(tasks.getByName("copyJar")) dependsOn(tasks.getByName("copyJar"))
from( from(
zipTree("build/app-classes/classes.jar"), zipTree("build/app-classes/classes.jar"),

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,13 @@ 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.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.Globals.TV import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.utils.AppContextUtils.openBrowser import com.lagradost.cloudstream3.utils.AppUtils.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
@ -35,7 +34,6 @@ import java.io.File
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.io.PrintStream import java.io.PrintStream
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 +80,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 +105,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 +151,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? {

View file

@ -5,7 +5,6 @@ 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
@ -37,7 +36,7 @@ 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.Globals.updateTv
import com.lagradost.cloudstream3.utils.AppContextUtils.isRtl import com.lagradost.cloudstream3.utils.AppUtils.isRtl
import com.lagradost.cloudstream3.utils.DataStoreHelper import com.lagradost.cloudstream3.utils.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
@ -165,7 +164,7 @@ object CommonActivity {
val toast = Toast(act) val toast = Toast(act)
toast.duration = duration ?: Toast.LENGTH_SHORT 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.view = binding.root
currentToast = toast currentToast = toast
toast.show() toast.show()
@ -277,35 +276,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 +464,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,26 +1,34 @@
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.ui.result.ResultViewModel2
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
/** /**
@ -29,11 +37,6 @@ import kotlin.math.absoluteValue
**/ **/
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 baseHeader = mapOf("User-Agent" to USER_AGENT)
val mapper = JsonMapper.builder().addModule(kotlinModule()) val mapper = JsonMapper.builder().addModule(kotlinModule())
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()!! .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()!!
@ -108,6 +111,17 @@ 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 {
// this fixes an issue with outdated api as getLoadResponseIdFromUrl might be fucked
return (if (this is ResultViewModel2.LoadResponseFromSearch) this.id else null)
?: getLoadResponseIdFromUrl(url, apiName)
}
/** /**
* Gets the website captcha token * 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 =
@ -260,6 +275,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 +622,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 +656,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 +664,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 +692,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,16 +723,27 @@ 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))
}
} }
fun MainAPI.fixUrlNull(url: String?): String? { fun MainAPI.fixUrlNull(url: String?): String? {
@ -594,6 +779,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
} }
@ -1015,25 +1204,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 +1232,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 +1257,30 @@ 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 +1362,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 +1375,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?) {
@ -1700,17 +1877,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 +1887,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 +1929,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,

View file

@ -44,6 +44,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 +59,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 +73,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
@ -83,22 +87,20 @@ 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.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,7 +110,6 @@ import com.lagradost.cloudstream3.ui.result.START_ACTION_RESUME_LATEST
import com.lagradost.cloudstream3.ui.result.SyncViewModel import com.lagradost.cloudstream3.ui.result.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
@ -121,23 +122,19 @@ 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.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.isAuthEnabled
@ -153,7 +150,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
@ -166,6 +162,8 @@ 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.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
@ -188,7 +186,8 @@ 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 { class MainActivity : AppCompatActivity(), ColorPickerDialogListener,
BiometricAuthenticator.BiometricAuthCallback {
companion object { 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"
@ -351,7 +350,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 +380,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 +402,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 +416,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 +472,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 +487,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
@ -573,44 +571,23 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
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 +627,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 +644,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) {
@ -770,7 +748,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 +771,14 @@ 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 +1129,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)
@ -1257,12 +1235,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()
}
} }
} }
} }
@ -1433,7 +1416,7 @@ 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
) )
@ -1448,13 +1431,13 @@ 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
) )
} }
@ -1464,12 +1447,12 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
?: SyncWatchType.NONE ?: 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()
} }
} }
@ -1550,26 +1533,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)
@ -1601,12 +1564,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
if (isLayout(TV or EMULATOR)) { if (isLayout(TV or EMULATOR)) {
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()
} }
} }
@ -1851,6 +1809,28 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
finish() finish()
} }
private var backPressedCallback: OnBackPressedCallback? = null
private fun attachBackPressedCallback() {
if (backPressedCallback == null) {
backPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
showConfirmExitDialog()
window?.navigationBarColor =
colorFromAttribute(R.attr.primaryGrayBackground)
updateLocale()
}
}
}
backPressedCallback?.isEnabled = true
onBackPressedDispatcher.addCallback(this, backPressedCallback ?: return)
}
private fun detachBackPressedCallback() {
backPressedCallback?.isEnabled = false
}
suspend fun checkGithubConnectivity(): Boolean { suspend fun checkGithubConnectivity(): Boolean {
return try { return try {
app.get( app.get(

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

@ -6,7 +6,6 @@ import com.lagradost.cloudstream3.extractors.helper.AesHelper.cryptoAESHandler
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"
@ -29,22 +28,17 @@ open class Chillx : ExtractorApi() {
override val requiresReferer = true override val requiresReferer = true
companion object { 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 { suspend fun fetchKey(): String {
return key return if (key != null) {
?: run { key!!
val res = } else {
app.get(keySource).parsedSafe<KeysData>() val fetch = app.get("https://raw.githubusercontent.com/rushi-chavan/multi-keys/keys/keys.json").parsedSafe<Keys>()?.key?.get(0) ?: throw ErrorLoadingException("Unable to get key")
?: throw ErrorLoadingException("Unable to get keys") key = fetch
key = res.keys.get(0) key!!
res.keys.get(0)
} }
} }
private data class KeysData(@JsonProperty("chillx") val keys: List<String>)
} }
@Suppress("NAME_SHADOWING") @Suppress("NAME_SHADOWING")
@ -103,4 +97,11 @@ open class Chillx : ExtractorApi() {
it.groupValues[1].toInt(16).toChar().toString() it.groupValues[1].toInt(16).toChar().toString()
} }
} }
data class Keys(
@JsonProperty("chillx") val key: List<String>
)
} }

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

@ -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

@ -22,11 +22,6 @@ class FourPlayRu : ContentX() {
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() { class FourPichive : ContentX() {
override var name = "FourPichive" override var name = "FourPichive"
override var mainUrl = "https://four.pichive.online" 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

@ -0,0 +1,65 @@
package com.lagradost.cloudstream3.extractors
import android.util.Base64
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.SubtitleFile
import com.lagradost.cloudstream3.amap
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
import java.net.URLDecoder
import javax.crypto.Cipher
import javax.crypto.spec.SecretKeySpec
class VidSrcTo : ExtractorApi() {
override val name = "VidSrcTo"
override val mainUrl = "https://vidsrc.to"
override val requiresReferer = true
override suspend fun getUrl(
url: String,
referer: String?,
subtitleCallback: (SubtitleFile) -> Unit,
callback: (ExtractorLink) -> Unit
) {
val mediaId = app.get(url).document.selectFirst("ul.episodes li a")?.attr("data-id") ?: return
val res = app.get("$mainUrl/ajax/embed/episode/$mediaId/sources").parsedSafe<VidsrctoEpisodeSources>() ?: return
if (res.status != 200) return
res.result?.amap { source ->
val embedRes = app.get("$mainUrl/ajax/embed/source/${source.id}").parsedSafe<VidsrctoEmbedSource>() ?: return@amap
val finalUrl = DecryptUrl(embedRes.result.encUrl)
if(finalUrl.equals(embedRes.result.encUrl)) return@amap
when (source.title) {
"Vidplay" -> AnyVidplay(finalUrl.substringBefore("/e/")).getUrl(finalUrl, referer, subtitleCallback, callback)
"Filemoon" -> FileMoon().getUrl(finalUrl, referer, subtitleCallback, callback)
}
}
}
private fun DecryptUrl(encUrl: String): String {
var data = encUrl.toByteArray()
data = Base64.decode(data, Base64.URL_SAFE)
val rc4Key = SecretKeySpec("WXrUARXb1aDLaZjI".toByteArray(), "RC4")
val cipher = Cipher.getInstance("RC4")
cipher.init(Cipher.DECRYPT_MODE, rc4Key, cipher.parameters)
data = cipher.doFinal(data)
return URLDecoder.decode(data.toString(Charsets.UTF_8), "utf-8")
}
data class VidsrctoEpisodeSources(
@JsonProperty("status") val status: Int,
@JsonProperty("result") val result: List<VidsrctoResult>?
)
data class VidsrctoResult(
@JsonProperty("id") val id: String,
@JsonProperty("title") val title: String
)
data class VidsrctoEmbedSource(
@JsonProperty("status") val status: Int,
@JsonProperty("result") val result: VidsrctoUrl
)
data class VidsrctoUrl(@JsonProperty("url") val encUrl: String)
}

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

@ -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.SubtitleFile import com.lagradost.cloudstream3.SubtitleFile
import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.AppUtils import com.lagradost.cloudstream3.utils.AppUtils
@ -87,7 +87,7 @@ open class Vidguardto : ExtractorApi() {
} }
Log.d("runJS", "Result: $result") Log.d("runJS", "Result: $result")
} catch (e: Exception) { } catch (e: Exception) {
Log.e("runJS", "Error executing JavaScript: ${e.message}") Log.e("runJS", "Error executing JavaScript", e)
} finally { } finally {
Context.exit() Context.exit()
} }

View file

@ -0,0 +1,120 @@
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 AnyVidplay(hostUrl: String) : Vidplay() {
override val mainUrl = hostUrl
}
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", referer = url).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

@ -1,9 +1,9 @@
package com.lagradost.cloudstream3.extractors package com.lagradost.cloudstream3.extractors
import android.util.Base64
import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.SubtitleFile
import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.base64Decode
import com.lagradost.cloudstream3.utils.AppUtils 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
@ -58,12 +58,12 @@ open class Voe : ExtractorApi() {
videoLinks.add( videoLinks.add(
when { when {
linkRegex.matches(link) -> link linkRegex.matches(link) -> link
else -> base64Decode(link) else -> String(Base64.decode(link, Base64.DEFAULT))
} }
) )
} else { } else {
val link2 = base64Regex.find(script)?.value ?: return val link2 = base64Regex.find(script)?.value ?: return
val decoded = base64Decode(link2) val decoded = Base64.decode(link2, Base64.DEFAULT).toString()
val videoLinkDTO = AppUtils.parseJson<WcoSources>(decoded) val videoLinkDTO = AppUtils.parseJson<WcoSources>(decoded)
videoLinkDTO.let { videoLinks.add(it.toString()) } videoLinkDTO.let { videoLinks.add(it.toString()) }
} }

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