mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
Compare commits
1 commit
master
...
revert-965
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04285b6ba2 |
450 changed files with 5492 additions and 13808 deletions
4
.github/ISSUE_TEMPLATE/application-bug.yml
vendored
4
.github/ISSUE_TEMPLATE/application-bug.yml
vendored
|
|
@ -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
|
||||||
|
|
|
||||||
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
|
|
@ -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.
|
||||||
|
|
|
||||||
6
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
6
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
|
|
@ -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
6
.github/locales.py
vendored
|
|
@ -1,7 +1,6 @@
|
||||||
import re
|
import re
|
||||||
import glob
|
import glob
|
||||||
import requests
|
import requests
|
||||||
import os
|
|
||||||
import lxml.etree as ET # builtin library doesn't preserve comments
|
import lxml.etree as ET # builtin library doesn't preserve comments
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -54,16 +53,11 @@ for file in glob.glob(f"{XML_NAME}*/strings.xml"):
|
||||||
try:
|
try:
|
||||||
tree = ET.parse(file)
|
tree = ET.parse(file)
|
||||||
for child in tree.getroot():
|
for child in tree.getroot():
|
||||||
if not child.text:
|
|
||||||
continue
|
|
||||||
if child.text.startswith("\\@string/"):
|
if child.text.startswith("\\@string/"):
|
||||||
print(f"[{file}] fixing {child.attrib['name']}")
|
print(f"[{file}] fixing {child.attrib['name']}")
|
||||||
child.text = child.text.replace("\\@string/", "@string/")
|
child.text = child.text.replace("\\@string/", "@string/")
|
||||||
with open(file, 'wb') as fp:
|
with open(file, 'wb') as fp:
|
||||||
fp.write(b'<?xml version="1.0" encoding="utf-8"?>\n')
|
fp.write(b'<?xml version="1.0" encoding="utf-8"?>\n')
|
||||||
tree.write(fp, encoding="utf-8", method="xml", pretty_print=True, xml_declaration=False)
|
tree.write(fp, encoding="utf-8", method="xml", pretty_print=True, xml_declaration=False)
|
||||||
# Remove trailing new line to be consistent with weblate
|
|
||||||
fp.seek(-1, os.SEEK_END)
|
|
||||||
fp.truncate()
|
|
||||||
except ET.ParseError as ex:
|
except ET.ParseError as ex:
|
||||||
print(f"[{file}] {ex}")
|
print(f"[{file}] {ex}")
|
||||||
|
|
|
||||||
7
.idea/gradle.xml
generated
7
.idea/gradle.xml
generated
|
|
@ -4,16 +4,17 @@
|
||||||
<component name="GradleSettings">
|
<component name="GradleSettings">
|
||||||
<option name="linkedExternalProjectsSettings">
|
<option name="linkedExternalProjectsSettings">
|
||||||
<GradleProjectSettings>
|
<GradleProjectSettings>
|
||||||
|
<option name="delegatedBuild" value="true" />
|
||||||
|
<option name="testRunner" value="GRADLE" />
|
||||||
|
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
|
<option name="gradleJvm" value="jbr-17" />
|
||||||
<option name="modules">
|
<option name="modules">
|
||||||
<set>
|
<set>
|
||||||
<option value="$PROJECT_DIR$" />
|
<option value="$PROJECT_DIR$" />
|
||||||
<option value="$PROJECT_DIR$/app" />
|
<option value="$PROJECT_DIR$/app" />
|
||||||
<option value="$PROJECT_DIR$/library" />
|
|
||||||
</set>
|
</set>
|
||||||
</option>
|
</option>
|
||||||
<option name="resolveExternalAnnotations" value="false" />
|
|
||||||
</GradleProjectSettings>
|
</GradleProjectSettings>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties
|
import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties
|
||||||
import org.jetbrains.dokka.gradle.DokkaTask
|
import org.jetbrains.dokka.gradle.DokkaTask
|
||||||
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.archivesName
|
|
||||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
|
@ -42,8 +41,8 @@ android {
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
if (prereleaseStoreFile != null) {
|
create("prerelease") {
|
||||||
create("prerelease") {
|
if (prereleaseStoreFile != null) {
|
||||||
storeFile = file(prereleaseStoreFile)
|
storeFile = file(prereleaseStoreFile)
|
||||||
storePassword = System.getenv("SIGNING_STORE_PASSWORD")
|
storePassword = System.getenv("SIGNING_STORE_PASSWORD")
|
||||||
keyAlias = System.getenv("SIGNING_KEY_ALIAS")
|
keyAlias = System.getenv("SIGNING_KEY_ALIAS")
|
||||||
|
|
@ -60,8 +59,8 @@ android {
|
||||||
minSdk = 21
|
minSdk = 21
|
||||||
targetSdk = 33 /* Android 14 is Fu*ked
|
targetSdk = 33 /* Android 14 is Fu*ked
|
||||||
^ https://developer.android.com/about/versions/14/behavior-changes-14#safer-dynamic-code-loading*/
|
^ https://developer.android.com/about/versions/14/behavior-changes-14#safer-dynamic-code-loading*/
|
||||||
versionCode = 64
|
versionCode = 63
|
||||||
versionName = "4.4.0"
|
versionName = "4.3.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() ?: "")
|
||||||
|
|
@ -71,9 +70,9 @@ android {
|
||||||
val localProperties = gradleLocalProperties(rootDir)
|
val localProperties = gradleLocalProperties(rootDir)
|
||||||
|
|
||||||
buildConfigField(
|
buildConfigField(
|
||||||
"long",
|
"String",
|
||||||
"BUILD_DATE",
|
"BUILDDATE",
|
||||||
"${System.currentTimeMillis()}"
|
"new java.text.SimpleDateFormat(\"yyyy-MM-dd HH:mm\").format(new java.util.Date(" + System.currentTimeMillis() + "L));"
|
||||||
)
|
)
|
||||||
buildConfigField(
|
buildConfigField(
|
||||||
"String",
|
"String",
|
||||||
|
|
@ -124,11 +123,7 @@ android {
|
||||||
resValue("bool", "is_prerelease", "true")
|
resValue("bool", "is_prerelease", "true")
|
||||||
buildConfigField("boolean", "BETA", "true")
|
buildConfigField("boolean", "BETA", "true")
|
||||||
applicationIdSuffix = ".prerelease"
|
applicationIdSuffix = ".prerelease"
|
||||||
if (signingConfigs.names.contains("prerelease")) {
|
signingConfig = signingConfigs.getByName("prerelease")
|
||||||
signingConfig = signingConfigs.getByName("prerelease")
|
|
||||||
} else {
|
|
||||||
logger.warn("No prerelease signing config!")
|
|
||||||
}
|
|
||||||
versionNameSuffix = "-PRE"
|
versionNameSuffix = "-PRE"
|
||||||
versionCode = (System.currentTimeMillis() / 60000).toInt()
|
versionCode = (System.currentTimeMillis() / 60000).toInt()
|
||||||
}
|
}
|
||||||
|
|
@ -159,24 +154,24 @@ repositories {
|
||||||
dependencies {
|
dependencies {
|
||||||
// Testing
|
// Testing
|
||||||
testImplementation("junit:junit:4.13.2")
|
testImplementation("junit:junit:4.13.2")
|
||||||
testImplementation("org.json:json:20240303")
|
testImplementation("org.json:json:20231013")
|
||||||
androidTestImplementation("androidx.test:core")
|
androidTestImplementation("androidx.test:core")
|
||||||
implementation("androidx.test.ext:junit-ktx:1.2.1")
|
implementation("androidx.test.ext:junit-ktx:1.1.5")
|
||||||
androidTestImplementation("androidx.test.ext:junit:1.2.1")
|
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
|
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
||||||
|
|
||||||
// Android Core & Lifecycle
|
// Android Core & Lifecycle
|
||||||
implementation("androidx.core:core-ktx:1.13.1")
|
implementation("androidx.core:core-ktx:1.12.0")
|
||||||
implementation("androidx.appcompat:appcompat:1.7.0")
|
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||||
implementation("androidx.navigation:navigation-ui-ktx:2.7.7")
|
implementation("androidx.navigation:navigation-ui-ktx:2.7.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
|
||||||
implementation("jp.wasabeef:glide-transformations:4.3.0")
|
implementation("jp.wasabeef:glide-transformations:4.3.0")
|
||||||
implementation("androidx.preference:preference-ktx:1.2.1")
|
implementation("androidx.preference:preference-ktx:1.2.1")
|
||||||
implementation("com.google.android.material:material:1.12.0")
|
implementation("com.google.android.material:material:1.11.0")
|
||||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
||||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
||||||
|
|
||||||
|
|
@ -186,9 +181,9 @@ dependencies {
|
||||||
implementation("com.github.bumptech.glide:okhttp3-integration:4.16.0")
|
implementation("com.github.bumptech.glide:okhttp3-integration:4.16.0")
|
||||||
|
|
||||||
// For KSP -> Official Annotation Processors are Not Yet Supported for KSP
|
// For KSP -> Official Annotation Processors are Not Yet Supported for KSP
|
||||||
ksp("dev.zacsweers.autoservice:auto-service-ksp:1.2.0")
|
ksp("dev.zacsweers.autoservice:auto-service-ksp:1.1.0")
|
||||||
implementation("com.google.guava:guava:33.2.1-android")
|
implementation("com.google.guava:guava:32.1.3-android")
|
||||||
implementation("dev.zacsweers.autoservice:auto-service-ksp:1.2.0")
|
implementation("dev.zacsweers.autoservice:auto-service-ksp:1.1.0")
|
||||||
|
|
||||||
// Media 3 (ExoPlayer)
|
// Media 3 (ExoPlayer)
|
||||||
implementation("androidx.media3:media3-ui:1.1.1")
|
implementation("androidx.media3:media3-ui:1.1.1")
|
||||||
|
|
@ -204,9 +199,9 @@ dependencies {
|
||||||
// PlayBack
|
// PlayBack
|
||||||
implementation("com.jaredrummler:colorpicker:1.1.0") // Subtitle Color Picker
|
implementation("com.jaredrummler:colorpicker:1.1.0") // Subtitle Color Picker
|
||||||
implementation("com.github.recloudstream:media-ffmpeg:1.1.0") // Custom FF-MPEG Lib for Audio Codecs
|
implementation("com.github.recloudstream:media-ffmpeg:1.1.0") // Custom FF-MPEG Lib for Audio Codecs
|
||||||
implementation("com.github.teamnewpipe:NewPipeExtractor:176da72") /* For Trailers
|
implementation("com.github.teamnewpipe:NewPipeExtractor:6dc25f7") /* For Trailers
|
||||||
^ Update to Latest Commits if Trailers Misbehave, github.com/TeamNewPipe/NewPipeExtractor/commits/dev */
|
^ Update to Latest Commits if Trailers Misbehave, github.com/TeamNewPipe/NewPipeExtractor/commits/dev */
|
||||||
implementation("com.github.albfernandez:juniversalchardet:2.5.0") // Subtitle Decoding
|
implementation("com.github.albfernandez:juniversalchardet:2.4.0") // Subtitle Decoding
|
||||||
|
|
||||||
// Crash Reports (AcraApplication.kt)
|
// Crash Reports (AcraApplication.kt)
|
||||||
implementation("ch.acra:acra-core:5.11.3")
|
implementation("ch.acra:acra-core:5.11.3")
|
||||||
|
|
@ -217,17 +212,18 @@ dependencies {
|
||||||
implementation("androidx.palette:palette-ktx:1.0.0") // Palette For Images -> Colors
|
implementation("androidx.palette:palette-ktx:1.0.0") // Palette For Images -> Colors
|
||||||
implementation("androidx.tvprovider:tvprovider:1.0.0")
|
implementation("androidx.tvprovider:tvprovider:1.0.0")
|
||||||
implementation("com.github.discord:OverlappingPanels:0.1.5") // Gestures
|
implementation("com.github.discord:OverlappingPanels:0.1.5") // Gestures
|
||||||
implementation("androidx.biometric:biometric:1.2.0-alpha05") // Fingerprint Authentication
|
implementation ("androidx.biometric:biometric:1.2.0-alpha05") // Fingerprint Authentication
|
||||||
implementation("com.github.rubensousa:previewseekbar-media3:1.1.1.0") // SeekBar Preview
|
implementation("com.github.rubensousa:previewseekbar-media3:1.1.1.0") // SeekBar Preview
|
||||||
implementation("io.github.g0dkar:qrcode-kotlin:4.2.0") // QR code for PIN Auth on TV
|
|
||||||
|
|
||||||
// Extensions & Other Libs
|
// Extensions & Other Libs
|
||||||
implementation("org.mozilla:rhino:1.7.15") // run JavaScript
|
implementation("org.mozilla:rhino:1.7.13") /* run JavaScript
|
||||||
|
^ Don't Bump RhinoJS to 1.7.14,`NoClassDefFoundError` Occurs and Trailers won't play (even with Desugaring)
|
||||||
|
NewPipeExtractor Issue */
|
||||||
implementation("me.xdrop:fuzzywuzzy:1.4.0") // Library/Ext Searching with Levenshtein Distance
|
implementation("me.xdrop:fuzzywuzzy:1.4.0") // Library/Ext Searching with Levenshtein Distance
|
||||||
implementation("com.github.LagradOst:SafeFile:0.0.6") // To Prevent the URI File Fu*kery
|
implementation("com.github.LagradOst:SafeFile:0.0.6") // To Prevent the URI File Fu*kery
|
||||||
implementation("org.conscrypt:conscrypt-android:2.5.2") // To Fix SSL Fu*kery on Android 9
|
implementation("org.conscrypt:conscrypt-android:2.5.2") // To Fix SSL Fu*kery on Android 9
|
||||||
implementation("com.uwetrottmann.tmdb2:tmdb-java:2.11.0") // TMDB API v3 Wrapper Made with RetroFit
|
implementation("com.uwetrottmann.tmdb2:tmdb-java:2.10.0") // TMDB API v3 Wrapper Made with RetroFit
|
||||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs_nio:2.0.4") //nio flavor needed for NewPipeExtractor
|
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4")
|
||||||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.1") /* JSON Parser
|
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.1") /* JSON Parser
|
||||||
^ Don't Bump Jackson above 2.13.1 , Crashes on Android TV's and FireSticks that have Min API
|
^ Don't Bump Jackson above 2.13.1 , Crashes on Android TV's and FireSticks that have Min API
|
||||||
Level 25 or Less. */
|
Level 25 or Less. */
|
||||||
|
|
@ -236,46 +232,18 @@ dependencies {
|
||||||
implementation("androidx.work:work-runtime:2.9.0")
|
implementation("androidx.work:work-runtime:2.9.0")
|
||||||
implementation("androidx.work:work-runtime-ktx:2.9.0")
|
implementation("androidx.work:work-runtime-ktx:2.9.0")
|
||||||
implementation("com.github.Blatzar:NiceHttp:0.4.11") // HTTP Lib
|
implementation("com.github.Blatzar:NiceHttp:0.4.11") // HTTP Lib
|
||||||
|
|
||||||
implementation(project(":library") {
|
|
||||||
// There does not seem to be a good way of getting the android flavor.
|
|
||||||
val isDebug = gradle.startParameter.taskRequests.any { task ->
|
|
||||||
task.args.any { arg ->
|
|
||||||
arg.contains("debug", true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.extra.set("isDebug", isDebug)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register<Jar>("androidSourcesJar") {
|
tasks.register("androidSourcesJar", Jar::class) {
|
||||||
archiveClassifier.set("sources")
|
archiveClassifier.set("sources")
|
||||||
from(android.sourceSets.getByName("main").java.srcDirs) // Full Sources
|
from(android.sourceSets.getByName("main").java.srcDirs) // Full Sources
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register<Copy>("copyJar") {
|
// For GradLew Plugin
|
||||||
from(
|
tasks.register("makeJar", Copy::class) {
|
||||||
"build/intermediates/compile_app_classes_jar/prereleaseDebug",
|
from("build/intermediates/compile_app_classes_jar/prereleaseDebug")
|
||||||
"../library/build/libs"
|
into("build")
|
||||||
)
|
include("classes.jar")
|
||||||
into("build/app-classes")
|
|
||||||
include("classes.jar", "library-jvm*.jar")
|
|
||||||
// Remove the version
|
|
||||||
rename("library-jvm.*.jar", "library-jvm.jar")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge the app classes and the library classes into classes.jar
|
|
||||||
tasks.register<Jar>("makeJar") {
|
|
||||||
// Duplicates cause hard to catch errors, better to fail at compile time.
|
|
||||||
duplicatesStrategy = DuplicatesStrategy.FAIL
|
|
||||||
dependsOn(tasks.getByName("copyJar"))
|
|
||||||
from(
|
|
||||||
zipTree("build/app-classes/classes.jar"),
|
|
||||||
zipTree("build/app-classes/library-jvm.jar")
|
|
||||||
)
|
|
||||||
destinationDirectory.set(layout.buildDirectory)
|
|
||||||
archivesName = "classes"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.withType<KotlinCompile> {
|
tasks.withType<KotlinCompile> {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
) { _, _ -> }
|
) { _, _ -> }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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? {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -30,14 +29,13 @@ import com.google.android.material.chip.ChipGroup
|
||||||
import com.google.android.material.navigationrail.NavigationRailView
|
import com.google.android.material.navigationrail.NavigationRailView
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
|
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
|
||||||
import com.lagradost.cloudstream3.MainActivity.Companion.resumeApps
|
|
||||||
import com.lagradost.cloudstream3.databinding.ToastBinding
|
import com.lagradost.cloudstream3.databinding.ToastBinding
|
||||||
import com.lagradost.cloudstream3.mvvm.logError
|
import com.lagradost.cloudstream3.mvvm.logError
|
||||||
import com.lagradost.cloudstream3.ui.player.PlayerEventType
|
import com.lagradost.cloudstream3.ui.player.PlayerEventType
|
||||||
import com.lagradost.cloudstream3.ui.result.ResultFragment
|
import com.lagradost.cloudstream3.ui.result.ResultFragment
|
||||||
import com.lagradost.cloudstream3.ui.result.UiText
|
import com.lagradost.cloudstream3.ui.result.UiText
|
||||||
import com.lagradost.cloudstream3.ui.settings.Globals.updateTv
|
import com.lagradost.cloudstream3.ui.settings.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 +163,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 +275,12 @@ object CommonActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateTheme(act: Activity) {
|
|
||||||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(act)
|
|
||||||
if (settingsManager
|
|
||||||
.getString(act.getString(R.string.app_theme_key), "AmoledLight") == "System"
|
|
||||||
&& Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
||||||
loadThemes(act)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun mapSystemTheme(act: Activity): Int {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
||||||
val currentNightMode =
|
|
||||||
act.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
|
|
||||||
return when (currentNightMode) {
|
|
||||||
Configuration.UI_MODE_NIGHT_NO -> R.style.LightMode // Night mode is not active, we're using the light theme
|
|
||||||
else -> R.style.AppTheme // Night mode is active, we're using dark theme
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return R.style.AppTheme
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadThemes(act: Activity?) {
|
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
|
||||||
|
|
@ -376,8 +351,8 @@ object CommonActivity {
|
||||||
currentLook = currentLook.parent as? View ?: break
|
currentLook = currentLook.parent as? View ?: break
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
private fun View.hasContent(): Boolean {
|
private fun View.hasContent() : Boolean {
|
||||||
return isShown && when (this) {
|
return isShown && when(this) {
|
||||||
//is RecyclerView -> this.childCount > 0
|
//is RecyclerView -> this.childCount > 0
|
||||||
is ViewGroup -> this.childCount > 0
|
is ViewGroup -> this.childCount > 0
|
||||||
else -> true
|
else -> true
|
||||||
|
|
@ -488,6 +463,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) {
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,43 +1,49 @@
|
||||||
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
|
||||||
|
|
||||||
|
const val USER_AGENT =
|
||||||
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36"
|
||||||
|
|
||||||
|
//val baseHeader = mapOf("User-Agent" to USER_AGENT)
|
||||||
|
val mapper = JsonMapper.builder().addModule(kotlinModule())
|
||||||
|
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()!!
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines the constant for the all languages preference, if this is set then it is
|
* Defines the constant for the all languages preference, if this is set then it is
|
||||||
* the equivalent of all languages being set
|
* the equivalent of all languages being set
|
||||||
**/
|
**/
|
||||||
const val AllLanguagesName = "universal"
|
const val AllLanguagesName = "universal"
|
||||||
|
|
||||||
const val USER_AGENT =
|
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36"
|
|
||||||
|
|
||||||
class ErrorLoadingException(message: String? = null) : Exception(message)
|
|
||||||
|
|
||||||
//val baseHeader = mapOf("User-Agent" to USER_AGENT)
|
|
||||||
val mapper = JsonMapper.builder().addModule(kotlinModule())
|
|
||||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()!!
|
|
||||||
|
|
||||||
object APIHolder {
|
object APIHolder {
|
||||||
val unixTime: Long
|
val unixTime: Long
|
||||||
get() = System.currentTimeMillis() / 1000L
|
get() = System.currentTimeMillis() / 1000L
|
||||||
|
|
@ -108,6 +114,16 @@ 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 +139,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 =
|
||||||
|
|
@ -205,15 +222,10 @@ object APIHolder {
|
||||||
} ?: false
|
} ?: false
|
||||||
|
|
||||||
val matchingTypes = types?.any { it.name.equals(media.format, true) } == true
|
val matchingTypes = types?.any { it.name.equals(media.format, true) } == true
|
||||||
if (lessAccurate) matchingTitles || matchingTypes && matchingYears else matchingTitles && matchingTypes && matchingYears
|
if(lessAccurate) matchingTitles || matchingTypes && matchingYears else matchingTitles && matchingTypes && matchingYears
|
||||||
} ?: return null
|
} ?: return null
|
||||||
|
|
||||||
Tracker(
|
Tracker(res.idMal, res.id.toString(), res.coverImage?.extraLarge ?: res.coverImage?.large, res.bannerImage)
|
||||||
res.idMal,
|
|
||||||
res.id.toString(),
|
|
||||||
res.coverImage?.extraLarge ?: res.coverImage?.large,
|
|
||||||
res.bannerImage
|
|
||||||
)
|
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
logError(t)
|
logError(t)
|
||||||
null
|
null
|
||||||
|
|
@ -260,6 +272,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 +619,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 +653,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 +661,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 +689,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,18 +720,31 @@ abstract class MainAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Might need a different implementation for desktop*/
|
/** Might need a different implementation for desktop*/
|
||||||
|
@SuppressLint("NewApi")
|
||||||
fun base64Decode(string: String): String {
|
fun base64Decode(string: String): String {
|
||||||
return String(base64DecodeArray(string), Charsets.ISO_8859_1)
|
return String(base64DecodeArray(string), Charsets.ISO_8859_1)
|
||||||
}
|
}
|
||||||
@OptIn(ExperimentalEncodingApi::class)
|
|
||||||
|
@SuppressLint("NewApi")
|
||||||
fun base64DecodeArray(string: String): ByteArray {
|
fun base64DecodeArray(string: String): ByteArray {
|
||||||
return Base64.decode(string)
|
return try {
|
||||||
|
android.util.Base64.decode(string, android.util.Base64.DEFAULT)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Base64.getDecoder().decode(string)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@OptIn(ExperimentalEncodingApi::class)
|
|
||||||
|
@SuppressLint("NewApi")
|
||||||
fun base64Encode(array: ByteArray): String {
|
fun base64Encode(array: ByteArray): String {
|
||||||
return Base64.encode(array)
|
return try {
|
||||||
|
String(android.util.Base64.encode(array, android.util.Base64.NO_WRAP), Charsets.ISO_8859_1)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
String(Base64.getEncoder().encode(array))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ErrorLoadingException(message: String? = null) : Exception(message)
|
||||||
|
|
||||||
fun MainAPI.fixUrlNull(url: String?): String? {
|
fun MainAPI.fixUrlNull(url: String?): String? {
|
||||||
if (url.isNullOrEmpty()) {
|
if (url.isNullOrEmpty()) {
|
||||||
return null
|
return null
|
||||||
|
|
@ -594,6 +778,10 @@ fun sortUrls(urls: Set<ExtractorLink>): List<ExtractorLink> {
|
||||||
return urls.sortedBy { t -> -t.quality }
|
return urls.sortedBy { t -> -t.quality }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun sortSubs(subs: Set<SubtitleData>): List<SubtitleData> {
|
||||||
|
return subs.sortedBy { it.name }
|
||||||
|
}
|
||||||
|
|
||||||
fun capitalizeString(str: String): String {
|
fun capitalizeString(str: String): String {
|
||||||
return capitalizeStringNullable(str) ?: str
|
return capitalizeStringNullable(str) ?: str
|
||||||
}
|
}
|
||||||
|
|
@ -677,12 +865,7 @@ enum class TvType(value: Int?) {
|
||||||
AsianDrama(9),
|
AsianDrama(9),
|
||||||
Live(10),
|
Live(10),
|
||||||
NSFW(11),
|
NSFW(11),
|
||||||
Others(12),
|
Others(12)
|
||||||
Music(13),
|
|
||||||
AudioBook(14),
|
|
||||||
|
|
||||||
/** Wont load the built in player, make your own interaction */
|
|
||||||
CustomMedia(15),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum class AutoDownloadMode(val value: Int) {
|
public enum class AutoDownloadMode(val value: Int) {
|
||||||
|
|
@ -1015,25 +1198,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 +1226,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 +1251,28 @@ interface LoadResponse {
|
||||||
|
|
||||||
fun LoadResponse.getImdbId(): String? {
|
fun LoadResponse.getImdbId(): String? {
|
||||||
return normalSafeApiCall {
|
return normalSafeApiCall {
|
||||||
readIdFromString(this.syncData[simklIdPrefix])[SimklSyncServices.Imdb]
|
SimklApi.readIdFromString(this.syncData[simklIdPrefix])?.get(SimklApi.Companion.SyncServices.Imdb)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun LoadResponse.getTMDbId(): String? {
|
fun LoadResponse.getTMDbId(): String? {
|
||||||
return normalSafeApiCall {
|
return normalSafeApiCall {
|
||||||
readIdFromString(this.syncData[simklIdPrefix])[SimklSyncServices.Tmdb]
|
SimklApi.readIdFromString(this.syncData[simklIdPrefix])?.get(SimklApi.Companion.SyncServices.Tmdb)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun LoadResponse.addMalId(id: Int?) {
|
fun LoadResponse.addMalId(id: Int?) {
|
||||||
this.syncData[malIdPrefix] = (id ?: return).toString()
|
this.syncData[malIdPrefix] = (id ?: return).toString()
|
||||||
this.addSimklId(SimklSyncServices.Mal, id.toString())
|
this.addSimklId(SimklApi.Companion.SyncServices.Mal, id.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun LoadResponse.addAniListId(id: Int?) {
|
fun LoadResponse.addAniListId(id: Int?) {
|
||||||
this.syncData[aniListIdPrefix] = (id ?: return).toString()
|
this.syncData[aniListIdPrefix] = (id ?: return).toString()
|
||||||
this.addSimklId(SimklSyncServices.AniList, id.toString())
|
this.addSimklId(SimklApi.Companion.SyncServices.AniList, id.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun LoadResponse.addSimklId(id: Int?) {
|
fun LoadResponse.addSimklId(id: Int?) {
|
||||||
this.addSimklId(SimklSyncServices.Simkl, id.toString())
|
this.addSimklId(SimklApi.Companion.SyncServices.Simkl, id.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun LoadResponse.addImdbUrl(url: String?) {
|
fun LoadResponse.addImdbUrl(url: String?) {
|
||||||
|
|
@ -1185,7 +1354,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 +1367,7 @@ interface LoadResponse {
|
||||||
|
|
||||||
fun LoadResponse.addTMDbId(id: String?) {
|
fun LoadResponse.addTMDbId(id: String?) {
|
||||||
// TODO add TMDb sync
|
// TODO add TMDb sync
|
||||||
this.addSimklId(SimklSyncServices.Tmdb, id)
|
this.addSimklId(SimklApi.Companion.SyncServices.Tmdb, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun LoadResponse.addRating(text: String?) {
|
fun LoadResponse.addRating(text: String?) {
|
||||||
|
|
@ -1277,24 +1446,11 @@ fun TvType?.isEpisodeBased(): Boolean {
|
||||||
return (this == TvType.TvSeries || this == TvType.Anime || this == TvType.AsianDrama)
|
return (this == TvType.TvSeries || this == TvType.Anime || this == TvType.AsianDrama)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
data class NextAiring(
|
data class NextAiring(
|
||||||
val episode: Int,
|
val episode: Int,
|
||||||
val unixTime: Long,
|
val unixTime: Long,
|
||||||
val season: Int? = null,
|
)
|
||||||
) {
|
|
||||||
/**
|
|
||||||
* Secondary constructor for backwards compatibility without season.
|
|
||||||
* TODO Remove this constructor after there is a new stable release and extensions are updated to support season.
|
|
||||||
*/
|
|
||||||
constructor(
|
|
||||||
episode: Int,
|
|
||||||
unixTime: Long,
|
|
||||||
) : this(
|
|
||||||
episode,
|
|
||||||
unixTime,
|
|
||||||
null
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param season To be mapped with episode season, not shown in UI if displaySeason is defined
|
* @param season To be mapped with episode season, not shown in UI if displaySeason is defined
|
||||||
|
|
@ -1385,26 +1541,8 @@ data class TorrentLoadResponse(
|
||||||
posterHeaders: Map<String, String>? = null,
|
posterHeaders: Map<String, String>? = null,
|
||||||
backgroundPosterUrl: String? = null,
|
backgroundPosterUrl: String? = null,
|
||||||
) : this(
|
) : this(
|
||||||
name,
|
name, url, apiName, magnet, torrent, plot, type, posterUrl, year, rating, tags, duration, trailers,
|
||||||
url,
|
recommendations, actors, comingSoon, syncData, posterHeaders, backgroundPosterUrl, null
|
||||||
apiName,
|
|
||||||
magnet,
|
|
||||||
torrent,
|
|
||||||
plot,
|
|
||||||
type,
|
|
||||||
posterUrl,
|
|
||||||
year,
|
|
||||||
rating,
|
|
||||||
tags,
|
|
||||||
duration,
|
|
||||||
trailers,
|
|
||||||
recommendations,
|
|
||||||
actors,
|
|
||||||
comingSoon,
|
|
||||||
syncData,
|
|
||||||
posterHeaders,
|
|
||||||
backgroundPosterUrl,
|
|
||||||
null
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1456,8 +1594,7 @@ data class AnimeLoadResponse(
|
||||||
return this.episodes.maxOf { (_, episodes) ->
|
return this.episodes.maxOf { (_, episodes) ->
|
||||||
episodes.count { episodeData ->
|
episodes.count { episodeData ->
|
||||||
// Prioritize display season as actual season may be something random to fit multiple seasons into one.
|
// Prioritize display season as actual season may be something random to fit multiple seasons into one.
|
||||||
val episodeSeason =
|
val episodeSeason = displayMap[episodeData.season] ?: episodeData.season ?: Int.MIN_VALUE
|
||||||
displayMap[episodeData.season] ?: episodeData.season ?: Int.MIN_VALUE
|
|
||||||
// Count all episodes from season 1 to below the current season.
|
// Count all episodes from season 1 to below the current season.
|
||||||
episodeSeason in 1..<season
|
episodeSeason in 1..<season
|
||||||
}
|
}
|
||||||
|
|
@ -1494,31 +1631,9 @@ data class AnimeLoadResponse(
|
||||||
seasonNames: List<SeasonData>? = null,
|
seasonNames: List<SeasonData>? = null,
|
||||||
backgroundPosterUrl: String? = null,
|
backgroundPosterUrl: String? = null,
|
||||||
) : this(
|
) : this(
|
||||||
engName,
|
engName, japName, name, url, apiName, type, posterUrl, year, episodes, showStatus, plot, tags,
|
||||||
japName,
|
synonyms, rating, duration, trailers, recommendations, actors, comingSoon, syncData, posterHeaders,
|
||||||
name,
|
nextAiring, seasonNames, backgroundPosterUrl, null
|
||||||
url,
|
|
||||||
apiName,
|
|
||||||
type,
|
|
||||||
posterUrl,
|
|
||||||
year,
|
|
||||||
episodes,
|
|
||||||
showStatus,
|
|
||||||
plot,
|
|
||||||
tags,
|
|
||||||
synonyms,
|
|
||||||
rating,
|
|
||||||
duration,
|
|
||||||
trailers,
|
|
||||||
recommendations,
|
|
||||||
actors,
|
|
||||||
comingSoon,
|
|
||||||
syncData,
|
|
||||||
posterHeaders,
|
|
||||||
nextAiring,
|
|
||||||
seasonNames,
|
|
||||||
backgroundPosterUrl,
|
|
||||||
null
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1650,7 +1765,7 @@ data class MovieLoadResponse(
|
||||||
backgroundPosterUrl: String? = null,
|
backgroundPosterUrl: String? = null,
|
||||||
) : this(
|
) : this(
|
||||||
name, url, apiName, type, dataUrl, posterUrl, year, plot, rating, tags, duration, trailers,
|
name, url, apiName, type, dataUrl, posterUrl, year, plot, rating, tags, duration, trailers,
|
||||||
recommendations, actors, comingSoon, syncData, posterHeaders, backgroundPosterUrl, null
|
recommendations, actors, comingSoon, syncData, posterHeaders, backgroundPosterUrl,null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1700,17 +1815,7 @@ suspend fun MainAPI.newMovieLoadResponse(
|
||||||
builder.initializer()
|
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 +1825,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 +1867,6 @@ fun <T> MainAPI.newEpisode(
|
||||||
return builder
|
return builder
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IDownloadableMinimum {
|
|
||||||
val url: String
|
|
||||||
val referer: String
|
|
||||||
val headers: Map<String, String>
|
|
||||||
}
|
|
||||||
|
|
||||||
fun IDownloadableMinimum.getId(): Int {
|
|
||||||
return url.hashCode()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set of sync services simkl is compatible with.
|
|
||||||
* Add more as required: https://simkl.docs.apiary.io/#reference/search/id-lookup/get-items-by-id
|
|
||||||
*/
|
|
||||||
enum class SimklSyncServices(val originalName: String) {
|
|
||||||
Simkl("simkl"),
|
|
||||||
Imdb("imdb"),
|
|
||||||
Tmdb("tmdb"),
|
|
||||||
AniList("anilist"),
|
|
||||||
Mal("mal"),
|
|
||||||
}
|
|
||||||
|
|
||||||
data class TvSeriesLoadResponse(
|
data class TvSeriesLoadResponse(
|
||||||
override var name: String,
|
override var name: String,
|
||||||
override var url: String,
|
override var url: String,
|
||||||
|
|
@ -1843,8 +1908,7 @@ data class TvSeriesLoadResponse(
|
||||||
|
|
||||||
return episodes.count { episodeData ->
|
return episodes.count { episodeData ->
|
||||||
// Prioritize display season as actual season may be something random to fit multiple seasons into one.
|
// Prioritize display season as actual season may be something random to fit multiple seasons into one.
|
||||||
val episodeSeason =
|
val episodeSeason = displayMap[episodeData.season] ?: episodeData.season ?: Int.MIN_VALUE
|
||||||
displayMap[episodeData.season] ?: episodeData.season ?: Int.MIN_VALUE
|
|
||||||
// Count all episodes from season 1 to below the current season.
|
// Count all episodes from season 1 to below the current season.
|
||||||
episodeSeason in 1..<season
|
episodeSeason in 1..<season
|
||||||
} + episode
|
} + episode
|
||||||
|
|
@ -1877,28 +1941,9 @@ data class TvSeriesLoadResponse(
|
||||||
seasonNames: List<SeasonData>? = null,
|
seasonNames: List<SeasonData>? = null,
|
||||||
backgroundPosterUrl: String? = null,
|
backgroundPosterUrl: String? = null,
|
||||||
) : this(
|
) : this(
|
||||||
name,
|
name, url, apiName, type, episodes, posterUrl, year, plot, showStatus, rating, tags, duration,
|
||||||
url,
|
trailers, recommendations, actors, comingSoon, syncData, posterHeaders, nextAiring, seasonNames,
|
||||||
apiName,
|
backgroundPosterUrl, null
|
||||||
type,
|
|
||||||
episodes,
|
|
||||||
posterUrl,
|
|
||||||
year,
|
|
||||||
plot,
|
|
||||||
showStatus,
|
|
||||||
rating,
|
|
||||||
tags,
|
|
||||||
duration,
|
|
||||||
trailers,
|
|
||||||
recommendations,
|
|
||||||
actors,
|
|
||||||
comingSoon,
|
|
||||||
syncData,
|
|
||||||
posterHeaders,
|
|
||||||
nextAiring,
|
|
||||||
seasonNames,
|
|
||||||
backgroundPosterUrl,
|
|
||||||
null
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1962,7 +2007,6 @@ data class AniSearch(
|
||||||
@JsonProperty("extraLarge") var extraLarge: String? = null,
|
@JsonProperty("extraLarge") var extraLarge: String? = null,
|
||||||
@JsonProperty("large") var large: String? = null,
|
@JsonProperty("large") var large: String? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
data class Title(
|
data class Title(
|
||||||
@JsonProperty("romaji") var romaji: String? = null,
|
@JsonProperty("romaji") var romaji: String? = null,
|
||||||
@JsonProperty("english") var english: String? = null,
|
@JsonProperty("english") var english: String? = null,
|
||||||
|
|
@ -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,27 +122,20 @@ import com.lagradost.cloudstream3.ui.settings.SettingsGeneral
|
||||||
import com.lagradost.cloudstream3.ui.setup.HAS_DONE_SETUP_KEY
|
import com.lagradost.cloudstream3.ui.setup.HAS_DONE_SETUP_KEY
|
||||||
import com.lagradost.cloudstream3.ui.setup.SetupFragmentExtensions
|
import com.lagradost.cloudstream3.ui.setup.SetupFragmentExtensions
|
||||||
import com.lagradost.cloudstream3.utils.ApkInstaller
|
import com.lagradost.cloudstream3.utils.ApkInstaller
|
||||||
import com.lagradost.cloudstream3.utils.AppContextUtils.getApiDubstatusSettings
|
import com.lagradost.cloudstream3.utils.AppUtils.html
|
||||||
import com.lagradost.cloudstream3.utils.AppContextUtils.html
|
import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable
|
||||||
import com.lagradost.cloudstream3.utils.AppContextUtils.isCastApiAvailable
|
import com.lagradost.cloudstream3.utils.AppUtils.isLtr
|
||||||
import com.lagradost.cloudstream3.utils.AppContextUtils.isLtr
|
import com.lagradost.cloudstream3.utils.AppUtils.isNetworkAvailable
|
||||||
import com.lagradost.cloudstream3.utils.AppContextUtils.isNetworkAvailable
|
import com.lagradost.cloudstream3.utils.AppUtils.isRtl
|
||||||
import com.lagradost.cloudstream3.utils.AppContextUtils.isRtl
|
import com.lagradost.cloudstream3.utils.AppUtils.loadCache
|
||||||
import com.lagradost.cloudstream3.utils.AppContextUtils.loadCache
|
import com.lagradost.cloudstream3.utils.AppUtils.loadRepository
|
||||||
import com.lagradost.cloudstream3.utils.AppContextUtils.loadRepository
|
import com.lagradost.cloudstream3.utils.AppUtils.loadResult
|
||||||
import com.lagradost.cloudstream3.utils.AppContextUtils.loadResult
|
import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
|
||||||
import com.lagradost.cloudstream3.utils.AppContextUtils.loadSearchResult
|
import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus
|
||||||
import com.lagradost.cloudstream3.utils.AppContextUtils.setDefaultFocus
|
|
||||||
import com.lagradost.cloudstream3.utils.AppContextUtils.updateHasTrailers
|
|
||||||
import com.lagradost.cloudstream3.utils.BackPressedCallbackHelper.attachBackPressedCallback
|
|
||||||
import com.lagradost.cloudstream3.utils.BackPressedCallbackHelper.detachBackPressedCallback
|
|
||||||
import com.lagradost.cloudstream3.utils.BackupUtils.backup
|
import com.lagradost.cloudstream3.utils.BackupUtils.backup
|
||||||
import com.lagradost.cloudstream3.utils.BackupUtils.setUpBackup
|
import com.lagradost.cloudstream3.utils.BackupUtils.setUpBackup
|
||||||
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.BiometricCallback
|
import com.lagradost.cloudstream3.utils.BiometricAuthenticator
|
||||||
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.biometricPrompt
|
|
||||||
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.deviceHasPasswordPinLock
|
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.deviceHasPasswordPinLock
|
||||||
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.isAuthEnabled
|
|
||||||
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.promptInfo
|
|
||||||
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.startBiometricAuthentication
|
import com.lagradost.cloudstream3.utils.BiometricAuthenticator.startBiometricAuthentication
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||||
|
|
@ -153,7 +147,6 @@ import com.lagradost.cloudstream3.utils.DataStoreHelper.migrateResumeWatching
|
||||||
import com.lagradost.cloudstream3.utils.Event
|
import com.lagradost.cloudstream3.utils.Event
|
||||||
import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate
|
import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate
|
||||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
|
||||||
import com.lagradost.cloudstream3.utils.SnackbarHelper.showSnackbar
|
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.changeStatusBarState
|
import com.lagradost.cloudstream3.utils.UIHelper.changeStatusBarState
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.checkWrite
|
import com.lagradost.cloudstream3.utils.UIHelper.checkWrite
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
|
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
|
||||||
|
|
@ -165,7 +158,8 @@ import com.lagradost.cloudstream3.utils.UIHelper.requestRW
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
||||||
import com.lagradost.cloudstream3.utils.USER_PROVIDER_API
|
import com.lagradost.cloudstream3.utils.USER_PROVIDER_API
|
||||||
import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API
|
import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API
|
||||||
import com.lagradost.cloudstream3.utils.fcast.FcastManager
|
import com.lagradost.nicehttp.Requests
|
||||||
|
import com.lagradost.nicehttp.ResponseParser
|
||||||
import com.lagradost.safefile.SafeFile
|
import com.lagradost.safefile.SafeFile
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
|
@ -176,6 +170,7 @@ import java.net.URLDecoder
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
import kotlin.reflect.KClass
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
//https://github.com/videolan/vlc-android/blob/3706c4be2da6800b3d26344fc04fab03ffa4b860/application/vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerActivity.kt#L1898
|
//https://github.com/videolan/vlc-android/blob/3706c4be2da6800b3d26344fc04fab03ffa4b860/application/vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerActivity.kt#L1898
|
||||||
|
|
@ -188,92 +183,117 @@ import kotlin.system.exitProcess
|
||||||
|
|
||||||
//https://github.com/jellyfin/jellyfin-android/blob/6cbf0edf84a3da82347c8d59b5d5590749da81a9/app/src/main/java/org/jellyfin/mobile/bridge/ExternalPlayer.kt#L225
|
//https://github.com/jellyfin/jellyfin-android/blob/6cbf0edf84a3da82347c8d59b5d5590749da81a9/app/src/main/java/org/jellyfin/mobile/bridge/ExternalPlayer.kt#L225
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCallback {
|
const val VLC_PACKAGE = "org.videolan.vlc"
|
||||||
|
const val MPV_PACKAGE = "is.xyz.mpv"
|
||||||
|
const val WEB_VIDEO_CAST_PACKAGE = "com.instantbits.cast.webvideo"
|
||||||
|
|
||||||
|
val VLC_COMPONENT = ComponentName(VLC_PACKAGE, "$VLC_PACKAGE.gui.video.VideoPlayerActivity")
|
||||||
|
val MPV_COMPONENT = ComponentName(MPV_PACKAGE, "$MPV_PACKAGE.MPVActivity")
|
||||||
|
|
||||||
|
//TODO REFACTOR AF
|
||||||
|
open class ResultResume(
|
||||||
|
val packageString: String,
|
||||||
|
val action: String = Intent.ACTION_VIEW,
|
||||||
|
val position: String? = null,
|
||||||
|
val duration: String? = null,
|
||||||
|
var launcher: ActivityResultLauncher<Intent>? = null,
|
||||||
|
) {
|
||||||
|
val defaultTime = -1L
|
||||||
|
|
||||||
|
val lastId get() = "${packageString}_last_open_id"
|
||||||
|
suspend fun launch(id: Int?, callback: suspend Intent.() -> Unit) {
|
||||||
|
val intent = Intent(action)
|
||||||
|
|
||||||
|
if (id != null)
|
||||||
|
setKey(lastId, id)
|
||||||
|
else
|
||||||
|
removeKey(lastId)
|
||||||
|
|
||||||
|
intent.setPackage(packageString)
|
||||||
|
callback.invoke(intent)
|
||||||
|
launcher?.launch(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun getPosition(intent: Intent?): Long {
|
||||||
|
return defaultTime
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun getDuration(intent: Intent?): Long {
|
||||||
|
return defaultTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val VLC = object : ResultResume(
|
||||||
|
VLC_PACKAGE,
|
||||||
|
// Android 13 intent restrictions fucks up specifically launching the VLC player
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
"org.videolan.vlc.player.result"
|
||||||
|
} else {
|
||||||
|
Intent.ACTION_VIEW
|
||||||
|
},
|
||||||
|
"extra_position",
|
||||||
|
"extra_duration",
|
||||||
|
) {
|
||||||
|
override fun getPosition(intent: Intent?): Long {
|
||||||
|
return intent?.getLongExtra(this.position, defaultTime) ?: defaultTime
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDuration(intent: Intent?): Long {
|
||||||
|
return intent?.getLongExtra(this.duration, defaultTime) ?: defaultTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val MPV = object : ResultResume(
|
||||||
|
MPV_PACKAGE,
|
||||||
|
//"is.xyz.mpv.MPVActivity.result", // resume not working :pensive:
|
||||||
|
position = "position",
|
||||||
|
duration = "duration",
|
||||||
|
) {
|
||||||
|
override fun getPosition(intent: Intent?): Long {
|
||||||
|
return intent?.getIntExtra(this.position, defaultTime.toInt())?.toLong() ?: defaultTime
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDuration(intent: Intent?): Long {
|
||||||
|
return intent?.getIntExtra(this.duration, defaultTime.toInt())?.toLong() ?: defaultTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val WEB_VIDEO = ResultResume(WEB_VIDEO_CAST_PACKAGE)
|
||||||
|
|
||||||
|
val resumeApps = arrayOf(
|
||||||
|
VLC, MPV, WEB_VIDEO
|
||||||
|
)
|
||||||
|
|
||||||
|
// Short name for requests client to make it nicer to use
|
||||||
|
|
||||||
|
var app = Requests(responseParser = object : ResponseParser {
|
||||||
|
val mapper: ObjectMapper = jacksonObjectMapper().configure(
|
||||||
|
DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun <T : Any> parse(text: String, kClass: KClass<T>): T {
|
||||||
|
return mapper.readValue(text, kClass.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun <T : Any> parseSafe(text: String, kClass: KClass<T>): T? {
|
||||||
|
return try {
|
||||||
|
mapper.readValue(text, kClass.java)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeValueAsString(obj: Any): String {
|
||||||
|
return mapper.writeValueAsString(obj)
|
||||||
|
}
|
||||||
|
}).apply {
|
||||||
|
defaultHeaders = mapOf("user-agent" to USER_AGENT)
|
||||||
|
}
|
||||||
|
|
||||||
|
class MainActivity : AppCompatActivity(), ColorPickerDialogListener,
|
||||||
|
BiometricAuthenticator.BiometricAuthCallback {
|
||||||
companion object {
|
companion object {
|
||||||
const val VLC_PACKAGE = "org.videolan.vlc"
|
|
||||||
const val MPV_PACKAGE = "is.xyz.mpv"
|
|
||||||
const val WEB_VIDEO_CAST_PACKAGE = "com.instantbits.cast.webvideo"
|
|
||||||
|
|
||||||
val VLC_COMPONENT = ComponentName(VLC_PACKAGE, "$VLC_PACKAGE.gui.video.VideoPlayerActivity")
|
|
||||||
val MPV_COMPONENT = ComponentName(MPV_PACKAGE, "$MPV_PACKAGE.MPVActivity")
|
|
||||||
|
|
||||||
//TODO REFACTOR AF
|
|
||||||
open class ResultResume(
|
|
||||||
val packageString: String,
|
|
||||||
val action: String = Intent.ACTION_VIEW,
|
|
||||||
val position: String? = null,
|
|
||||||
val duration: String? = null,
|
|
||||||
var launcher: ActivityResultLauncher<Intent>? = null,
|
|
||||||
) {
|
|
||||||
val defaultTime = -1L
|
|
||||||
|
|
||||||
val lastId get() = "${packageString}_last_open_id"
|
|
||||||
suspend fun launch(id: Int?, callback: suspend Intent.() -> Unit) {
|
|
||||||
val intent = Intent(action)
|
|
||||||
|
|
||||||
if (id != null)
|
|
||||||
setKey(lastId, id)
|
|
||||||
else
|
|
||||||
removeKey(lastId)
|
|
||||||
|
|
||||||
intent.setPackage(packageString)
|
|
||||||
callback.invoke(intent)
|
|
||||||
launcher?.launch(intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun getPosition(intent: Intent?): Long {
|
|
||||||
return defaultTime
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun getDuration(intent: Intent?): Long {
|
|
||||||
return defaultTime
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val VLC = object : ResultResume(
|
|
||||||
VLC_PACKAGE,
|
|
||||||
// Android 13 intent restrictions fucks up specifically launching the VLC player
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
|
||||||
"org.videolan.vlc.player.result"
|
|
||||||
} else {
|
|
||||||
Intent.ACTION_VIEW
|
|
||||||
},
|
|
||||||
"extra_position",
|
|
||||||
"extra_duration",
|
|
||||||
) {
|
|
||||||
override fun getPosition(intent: Intent?): Long {
|
|
||||||
return intent?.getLongExtra(this.position, defaultTime) ?: defaultTime
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getDuration(intent: Intent?): Long {
|
|
||||||
return intent?.getLongExtra(this.duration, defaultTime) ?: defaultTime
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val MPV = object : ResultResume(
|
|
||||||
MPV_PACKAGE,
|
|
||||||
//"is.xyz.mpv.MPVActivity.result", // resume not working :pensive:
|
|
||||||
position = "position",
|
|
||||||
duration = "duration",
|
|
||||||
) {
|
|
||||||
override fun getPosition(intent: Intent?): Long {
|
|
||||||
return intent?.getIntExtra(this.position, defaultTime.toInt())?.toLong()
|
|
||||||
?: defaultTime
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getDuration(intent: Intent?): Long {
|
|
||||||
return intent?.getIntExtra(this.duration, defaultTime.toInt())?.toLong()
|
|
||||||
?: defaultTime
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val WEB_VIDEO = ResultResume(WEB_VIDEO_CAST_PACKAGE)
|
|
||||||
|
|
||||||
val resumeApps = arrayOf(
|
|
||||||
VLC, MPV, WEB_VIDEO
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
const val TAG = "MAINACT"
|
const val TAG = "MAINACT"
|
||||||
const val ANIMATED_OUTLINE: Boolean = false
|
const val ANIMATED_OUTLINE: Boolean = false
|
||||||
var lastError: String? = null
|
var lastError: String? = null
|
||||||
|
|
@ -351,7 +371,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 +401,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 +423,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 +437,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 +493,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 +508,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 +592,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 +648,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 +665,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (isCastApiAvailable()) {
|
if (isCastApiAvailable()) {
|
||||||
mSessionManager?.removeSessionManagerListener(mSessionManagerListener)
|
mSessionManager.removeSessionManagerListener(mSessionManagerListener)
|
||||||
//mCastSession = null
|
//mCastSession = null
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|
@ -674,7 +673,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
|
||||||
val response = CommonActivity.dispatchKeyEvent(this, event)
|
val response = CommonActivity.dispatchKeyEvent(this, event)
|
||||||
if (response != null)
|
if (response != null)
|
||||||
return response
|
return response
|
||||||
|
|
@ -770,7 +769,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 +792,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 +1150,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)
|
||||||
|
|
@ -1232,17 +1231,18 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
|
||||||
changeStatusBarState(isLayout(EMULATOR))
|
changeStatusBarState(isLayout(EMULATOR))
|
||||||
|
|
||||||
/** Biometric stuff for users without accounts **/
|
/** Biometric stuff for users without accounts **/
|
||||||
|
val authEnabled = settingsManager.getBoolean(getString(R.string.biometric_key), false)
|
||||||
val noAccounts = settingsManager.getBoolean(
|
val noAccounts = settingsManager.getBoolean(
|
||||||
getString(R.string.skip_startup_account_select_key),
|
getString(R.string.skip_startup_account_select_key),
|
||||||
false
|
false
|
||||||
) || accounts.count() <= 1
|
) || accounts.count() <= 1
|
||||||
|
|
||||||
if (isLayout(PHONE) && isAuthEnabled(this) && noAccounts) {
|
if (isLayout(PHONE) && authEnabled && noAccounts) {
|
||||||
if (deviceHasPasswordPinLock(this)) {
|
if (deviceHasPasswordPinLock(this)) {
|
||||||
startBiometricAuthentication(this, R.string.biometric_authentication_title, false)
|
startBiometricAuthentication(this, R.string.biometric_authentication_title, false)
|
||||||
|
|
||||||
promptInfo?.let { prompt ->
|
BiometricAuthenticator.promptInfo?.let { promt ->
|
||||||
biometricPrompt?.authenticate(prompt)
|
BiometricAuthenticator.biometricPrompt?.authenticate(promt)
|
||||||
}
|
}
|
||||||
|
|
||||||
// hide background while authenticating, Sorry moms & dads 🙏
|
// hide background while authenticating, Sorry moms & dads 🙏
|
||||||
|
|
@ -1257,12 +1257,17 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
|
||||||
this.setKey(getString(R.string.jsdelivr_proxy_key), false)
|
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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1395,7 +1400,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
observe(viewModel.watchStatus, ::setWatchStatus)
|
observe(viewModel.watchStatus,::setWatchStatus)
|
||||||
observe(syncViewModel.userData, ::setUserData)
|
observe(syncViewModel.userData, ::setUserData)
|
||||||
observeNullable(viewModel.subscribeStatus, ::setSubscribeStatus)
|
observeNullable(viewModel.subscribeStatus, ::setSubscribeStatus)
|
||||||
|
|
||||||
|
|
@ -1433,7 +1438,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 +1453,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 +1469,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 +1555,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 +1586,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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1774,8 +1754,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
|
||||||
runAutoUpdate()
|
runAutoUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
FcastManager().init(this, false)
|
|
||||||
|
|
||||||
APIRepository.dubStatusActive = getApiDubstatusSettings()
|
APIRepository.dubStatusActive = getApiDubstatusSettings()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -1847,8 +1825,26 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
|
||||||
binding?.navHostFragment?.isInvisible = false
|
binding?.navHostFragment?.isInvisible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAuthenticationError() {
|
private var backPressedCallback: OnBackPressedCallback? = null
|
||||||
finish()
|
|
||||||
|
private fun attachBackPressedCallback() {
|
||||||
|
if (backPressedCallback == null) {
|
||||||
|
backPressedCallback = object : OnBackPressedCallback(true) {
|
||||||
|
override fun handleOnBackPressed() {
|
||||||
|
showConfirmExitDialog()
|
||||||
|
window?.navigationBarColor =
|
||||||
|
colorFromAttribute(R.attr.primaryGrayBackground)
|
||||||
|
updateLocale()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
backPressedCallback?.isEnabled = true
|
||||||
|
onBackPressedDispatcher.addCallback(this, backPressedCallback ?: return)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun detachBackPressedCallback() {
|
||||||
|
backPressedCallback?.isEnabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun checkGithubConnectivity(): Boolean {
|
suspend fun checkGithubConnectivity(): Boolean {
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
@ -2,11 +2,12 @@ package com.lagradost.cloudstream3.extractors
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
import com.lagradost.cloudstream3.*
|
import com.lagradost.cloudstream3.*
|
||||||
|
import com.lagradost.cloudstream3.extractors.helper.*
|
||||||
import com.lagradost.cloudstream3.extractors.helper.AesHelper.cryptoAESHandler
|
import com.lagradost.cloudstream3.extractors.helper.AesHelper.cryptoAESHandler
|
||||||
|
import com.lagradost.cloudstream3.utils.AppUtils
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorApi
|
import com.lagradost.cloudstream3.utils.ExtractorApi
|
||||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
import com.lagradost.cloudstream3.utils.M3u8Helper
|
import com.lagradost.cloudstream3.utils.M3u8Helper
|
||||||
import kotlin.run
|
|
||||||
|
|
||||||
class Moviesapi : Chillx() {
|
class Moviesapi : Chillx() {
|
||||||
override val name = "Moviesapi"
|
override val name = "Moviesapi"
|
||||||
|
|
@ -27,44 +28,30 @@ open class Chillx : ExtractorApi() {
|
||||||
override val name = "Chillx"
|
override val name = "Chillx"
|
||||||
override val mainUrl = "https://chillx.top"
|
override val mainUrl = "https://chillx.top"
|
||||||
override val requiresReferer = true
|
override val requiresReferer = true
|
||||||
|
private var key: String? = null
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val keySource = "https://rowdy-avocado.github.io/multi-keys/"
|
|
||||||
|
|
||||||
private var key: String? = null
|
|
||||||
|
|
||||||
private suspend fun fetchKey(): String {
|
|
||||||
return key
|
|
||||||
?: run {
|
|
||||||
val res =
|
|
||||||
app.get(keySource).parsedSafe<KeysData>()
|
|
||||||
?: throw ErrorLoadingException("Unable to get keys")
|
|
||||||
key = res.keys.get(0)
|
|
||||||
res.keys.get(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private data class KeysData(@JsonProperty("chillx") val keys: List<String>)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("NAME_SHADOWING")
|
|
||||||
override suspend fun getUrl(
|
override suspend fun getUrl(
|
||||||
url: String,
|
url: String,
|
||||||
referer: String?,
|
referer: String?,
|
||||||
subtitleCallback: (SubtitleFile) -> Unit,
|
subtitleCallback: (SubtitleFile) -> Unit,
|
||||||
callback: (ExtractorLink) -> Unit
|
callback: (ExtractorLink) -> Unit
|
||||||
) {
|
) {
|
||||||
val master = Regex("""JScript[\w+]?\s*=\s*'([^']+)""").find(
|
val master = Regex("\\s*=\\s*'([^']+)").find(
|
||||||
app.get(
|
app.get(
|
||||||
url,
|
url,
|
||||||
referer = url,
|
referer = referer ?: "",
|
||||||
|
headers = mapOf(
|
||||||
|
"Accept" to "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
||||||
|
"Accept-Language" to "en-US,en;q=0.5",
|
||||||
|
)
|
||||||
).text
|
).text
|
||||||
)?.groupValues?.get(1)
|
)?.groupValues?.get(1)
|
||||||
val key = fetchKey()
|
val decrypt = cryptoAESHandler(master ?: return, getKey().toByteArray(), false)?.replace("\\", "") ?: throw ErrorLoadingException("failed to decrypt")
|
||||||
val decrypt = cryptoAESHandler(master ?: "", key.toByteArray(), false)?.replace("\\", "") ?: throw ErrorLoadingException("failed to decrypt")
|
|
||||||
val source = Regex(""""?file"?:\s*"([^"]+)""").find(decrypt)?.groupValues?.get(1)
|
val source = Regex(""""?file"?:\s*"([^"]+)""").find(decrypt)?.groupValues?.get(1)
|
||||||
|
|
||||||
val subtitles = Regex("""subtitle"?:\s*"([^"]+)""").find(decrypt)?.groupValues?.get(1)
|
val subtitles = Regex("""subtitle"?:\s*"([^"]+)""").find(decrypt)?.groupValues?.get(1)
|
||||||
val subtitlePattern = """\[(.*?)](https?://[^\s,]+)""".toRegex()
|
val subtitlePattern = """\[(.*?)\](https?://[^\s,]+)""".toRegex()
|
||||||
val matches = subtitlePattern.findAll(subtitles ?: "")
|
val matches = subtitlePattern.findAll(subtitles ?: "")
|
||||||
val languageUrlPairs = matches.map { matchResult ->
|
val languageUrlPairs = matches.map { matchResult ->
|
||||||
val (language, url) = matchResult.destructured
|
val (language, url) = matchResult.destructured
|
||||||
|
|
@ -103,4 +90,16 @@ open class Chillx : ExtractorApi() {
|
||||||
it.groupValues[1].toInt(16).toChar().toString()
|
it.groupValues[1].toInt(16).toChar().toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getKey() = key ?: fetchKey().also { key = it }
|
||||||
|
|
||||||
|
private suspend fun fetchKey(): String {
|
||||||
|
return app.get("https://raw.githubusercontent.com/Sofie99/Resources/main/chillix_key.json").parsed()
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Tracks(
|
||||||
|
@JsonProperty("file") val file: String? = null,
|
||||||
|
@JsonProperty("label") val label: String? = null,
|
||||||
|
@JsonProperty("kind") val kind: String? = null,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,16 +9,10 @@ import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8
|
import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
|
||||||
class Geodailymotion : Dailymotion() {
|
|
||||||
override val name = "GeoDailymotion"
|
|
||||||
override val mainUrl = "https://geo.dailymotion.com"
|
|
||||||
}
|
|
||||||
|
|
||||||
open class Dailymotion : ExtractorApi() {
|
open class Dailymotion : ExtractorApi() {
|
||||||
override val mainUrl = "https://www.dailymotion.com"
|
override val mainUrl = "https://www.dailymotion.com"
|
||||||
override val name = "Dailymotion"
|
override val name = "Dailymotion"
|
||||||
override val requiresReferer = false
|
override val requiresReferer = false
|
||||||
private val baseUrl = "https://www.dailymotion.com"
|
|
||||||
|
|
||||||
@Suppress("RegExpSimplifiable")
|
@Suppress("RegExpSimplifiable")
|
||||||
private val videoIdRegex = "^[kx][a-zA-Z0-9]+\$".toRegex()
|
private val videoIdRegex = "^[kx][a-zA-Z0-9]+\$".toRegex()
|
||||||
|
|
@ -40,7 +34,7 @@ open class Dailymotion : ExtractorApi() {
|
||||||
val dmV1st = config.dmInternalData.v1st
|
val dmV1st = config.dmInternalData.v1st
|
||||||
val dmTs = config.dmInternalData.ts
|
val dmTs = config.dmInternalData.ts
|
||||||
val embedder = config.context.embedder
|
val embedder = config.context.embedder
|
||||||
val metaDataUrl = "$baseUrl/player/metadata/video/$id?embedder=$embedder&locale=en-US&dmV1st=$dmV1st&dmTs=$dmTs&is_native_app=0"
|
val metaDataUrl = "$mainUrl/player/metadata/video/$id?embedder=$embedder&locale=en-US&dmV1st=$dmV1st&dmTs=$dmTs&is_native_app=0"
|
||||||
val metaData = app.get(metaDataUrl, referer = embedUrl, cookies = req.cookies)
|
val metaData = app.get(metaDataUrl, referer = embedUrl, cookies = req.cookies)
|
||||||
.parsedSafe<MetaData>() ?: return
|
.parsedSafe<MetaData>() ?: return
|
||||||
metaData.qualities.forEach { (_, video) ->
|
metaData.qualities.forEach { (_, video) ->
|
||||||
|
|
@ -51,19 +45,16 @@ open class Dailymotion : ExtractorApi() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getEmbedUrl(url: String): String? {
|
private fun getEmbedUrl(url: String): String? {
|
||||||
if (url.contains("/embed/") || url.contains("/video/")) {
|
if (url.contains("/embed/")) {
|
||||||
return url
|
return url
|
||||||
|
}
|
||||||
|
val vid = getVideoId(url) ?: return null
|
||||||
|
return "$mainUrl/embed/video/$vid"
|
||||||
}
|
}
|
||||||
if (url.contains("geo.dailymotion.com")) {
|
|
||||||
val videoId = url.substringAfter("video=")
|
|
||||||
return "$baseUrl/embed/video/$videoId"
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getVideoId(url: String): String? {
|
private fun getVideoId(url: String): String? {
|
||||||
val path = URL(url).path
|
val path = URL(url).path
|
||||||
val id = path.substringAfter("/video/")
|
val id = path.substringAfter("video/")
|
||||||
if (id.matches(videoIdRegex)) {
|
if (id.matches(videoIdRegex)) {
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
@ -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(
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
@ -21,13 +21,3 @@ class FourPlayRu : ContentX() {
|
||||||
override var name = "FourPlayRu"
|
override var name = "FourPlayRu"
|
||||||
override var mainUrl = "https://four.playru.net"
|
override var mainUrl = "https://four.playru.net"
|
||||||
}
|
}
|
||||||
|
|
||||||
class Pichive : ContentX() {
|
|
||||||
override var name = "Pichive"
|
|
||||||
override var mainUrl = "https://pichive.online"
|
|
||||||
}
|
|
||||||
|
|
||||||
class FourPichive : ContentX() {
|
|
||||||
override var name = "FourPichive"
|
|
||||||
override var mainUrl = "https://four.pichive.online"
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
@ -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("\\"", "\"").replace("\\\\", "\\")
|
val video_req = app.get(url, headers=user_agent).text.replace("\\"", "\"").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
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
@ -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.*
|
||||||
|
|
@ -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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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()
|
||||||
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
@ -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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
@ -25,13 +25,9 @@ open class Vidmoly : ExtractorApi() {
|
||||||
subtitleCallback: (SubtitleFile) -> Unit,
|
subtitleCallback: (SubtitleFile) -> Unit,
|
||||||
callback: (ExtractorLink) -> Unit
|
callback: (ExtractorLink) -> Unit
|
||||||
) {
|
) {
|
||||||
val headers = mapOf(
|
|
||||||
"User-Agent" to "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36",
|
|
||||||
"Sec-Fetch-Dest" to "iframe"
|
|
||||||
)
|
|
||||||
val script = app.get(
|
val script = app.get(
|
||||||
url,
|
url,
|
||||||
headers = headers,
|
|
||||||
referer = referer,
|
referer = referer,
|
||||||
).document.select("script")
|
).document.select("script")
|
||||||
.find { it.data().contains("sources:") }?.data()
|
.find { it.data().contains("sources:") }?.data()
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
package com.lagradost.cloudstream3.extractors
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.lagradost.cloudstream3.SubtitleFile
|
||||||
|
import com.lagradost.cloudstream3.app
|
||||||
|
import com.lagradost.cloudstream3.base64Encode
|
||||||
|
import com.lagradost.cloudstream3.utils.ExtractorApi
|
||||||
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
|
import com.lagradost.cloudstream3.utils.M3u8Helper
|
||||||
|
import javax.crypto.Cipher
|
||||||
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
|
||||||
|
// Code found in https://github.com/KillerDogeEmpire/vidplay-keys
|
||||||
|
// special credits to @KillerDogeEmpire for providing key
|
||||||
|
|
||||||
|
class MyCloud : Vidplay() {
|
||||||
|
override val name = "MyCloud"
|
||||||
|
override val mainUrl = "https://mcloud.bz"
|
||||||
|
}
|
||||||
|
|
||||||
|
class VidplayOnline : Vidplay() {
|
||||||
|
override val mainUrl = "https://vidplay.online"
|
||||||
|
}
|
||||||
|
|
||||||
|
open class Vidplay : ExtractorApi() {
|
||||||
|
override val name = "Vidplay"
|
||||||
|
override val mainUrl = "https://vidplay.site"
|
||||||
|
override val requiresReferer = true
|
||||||
|
open val key =
|
||||||
|
"https://raw.githubusercontent.com/KillerDogeEmpire/vidplay-keys/keys/keys.json"
|
||||||
|
|
||||||
|
override suspend fun getUrl(
|
||||||
|
url: String,
|
||||||
|
referer: String?,
|
||||||
|
subtitleCallback: (SubtitleFile) -> Unit,
|
||||||
|
callback: (ExtractorLink) -> Unit
|
||||||
|
) {
|
||||||
|
val id = url.substringBefore("?").substringAfterLast("/")
|
||||||
|
val encodeId = encodeId(id, getKeys())
|
||||||
|
val mediaUrl = callFutoken(encodeId, url)
|
||||||
|
val res = app.get(
|
||||||
|
"$mediaUrl", headers = mapOf(
|
||||||
|
"Accept" to "application/json, text/javascript, */*; q=0.01",
|
||||||
|
"X-Requested-With" to "XMLHttpRequest",
|
||||||
|
), referer = url
|
||||||
|
).parsedSafe<Response>()?.result
|
||||||
|
|
||||||
|
res?.sources?.map {
|
||||||
|
M3u8Helper.generateM3u8(
|
||||||
|
this.name,
|
||||||
|
it.file ?: return@map,
|
||||||
|
"$mainUrl/"
|
||||||
|
).forEach(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
res?.tracks?.filter { it.kind == "captions" }?.map {
|
||||||
|
subtitleCallback.invoke(
|
||||||
|
SubtitleFile(it.label ?: return@map, it.file ?: return@map)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getKeys(): List<String> {
|
||||||
|
return app.get(key).parsed()
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun callFutoken(id: String, url: String): String? {
|
||||||
|
val script = app.get("$mainUrl/futoken").text
|
||||||
|
val k = "k='(\\S+)'".toRegex().find(script)?.groupValues?.get(1) ?: return null
|
||||||
|
val a = mutableListOf(k)
|
||||||
|
for (i in id.indices) {
|
||||||
|
a.add((k[i % k.length].code + id[i].code).toString())
|
||||||
|
}
|
||||||
|
return "$mainUrl/mediainfo/${a.joinToString(",")}?${url.substringAfter("?")}"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun encodeId(id: String, keyList: List<String>): String {
|
||||||
|
val cipher1 = Cipher.getInstance("RC4")
|
||||||
|
val cipher2 = Cipher.getInstance("RC4")
|
||||||
|
cipher1.init(
|
||||||
|
Cipher.DECRYPT_MODE,
|
||||||
|
SecretKeySpec(keyList[0].toByteArray(), "RC4"),
|
||||||
|
cipher1.parameters
|
||||||
|
)
|
||||||
|
cipher2.init(
|
||||||
|
Cipher.DECRYPT_MODE,
|
||||||
|
SecretKeySpec(keyList[1].toByteArray(), "RC4"),
|
||||||
|
cipher2.parameters
|
||||||
|
)
|
||||||
|
var input = id.toByteArray()
|
||||||
|
input = cipher1.doFinal(input)
|
||||||
|
input = cipher2.doFinal(input)
|
||||||
|
return base64Encode(input).replace("/", "_")
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Tracks(
|
||||||
|
@JsonProperty("file") val file: String? = null,
|
||||||
|
@JsonProperty("label") val label: String? = null,
|
||||||
|
@JsonProperty("kind") val kind: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Sources(
|
||||||
|
@JsonProperty("file") val file: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Result(
|
||||||
|
@JsonProperty("sources") val sources: ArrayList<Sources>? = arrayListOf(),
|
||||||
|
@JsonProperty("tracks") val tracks: ArrayList<Tracks>? = arrayListOf(),
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Response(
|
||||||
|
@JsonProperty("result") val result: Result? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
package com.lagradost.cloudstream3.extractors
|
||||||
|
|
||||||
|
import com.lagradost.cloudstream3.SubtitleFile
|
||||||
|
import com.lagradost.cloudstream3.app
|
||||||
|
import com.lagradost.cloudstream3.utils.ExtractorApi
|
||||||
|
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||||
|
import com.lagradost.cloudstream3.utils.M3u8Helper
|
||||||
|
|
||||||
|
class Tubeless : Voe() {
|
||||||
|
override var mainUrl = "https://tubelessceliolymph.com"
|
||||||
|
}
|
||||||
|
|
||||||
|
open class Voe : ExtractorApi() {
|
||||||
|
override val name = "Voe"
|
||||||
|
override val mainUrl = "https://voe.sx"
|
||||||
|
override val requiresReferer = true
|
||||||
|
|
||||||
|
override suspend fun getUrl(
|
||||||
|
url: String,
|
||||||
|
referer: String?,
|
||||||
|
subtitleCallback: (SubtitleFile) -> Unit,
|
||||||
|
callback: (ExtractorLink) -> Unit
|
||||||
|
) {
|
||||||
|
val res = app.get(url, referer = referer).document
|
||||||
|
val script = res.select("script").find { it.data().contains("sources =") }?.data()
|
||||||
|
val link = Regex("[\"']hls[\"']:\\s*[\"'](.*)[\"']").find(script ?: return)?.groupValues?.get(1)
|
||||||
|
|
||||||
|
M3u8Helper.generateM3u8(
|
||||||
|
name,
|
||||||
|
link ?: return,
|
||||||
|
"$mainUrl/",
|
||||||
|
headers = mapOf("Origin" to "$mainUrl/")
|
||||||
|
).forEach(callback)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue