diff --git a/.github/downloads.jpg b/.github/downloads.jpg
new file mode 100644
index 00000000..ca14a664
Binary files /dev/null and b/.github/downloads.jpg differ
diff --git a/.github/home.jpg b/.github/home.jpg
new file mode 100644
index 00000000..72370d3c
Binary files /dev/null and b/.github/home.jpg differ
diff --git a/.github/locales.py b/.github/locales.py
deleted file mode 100644
index 7d6d6b90..00000000
--- a/.github/locales.py
+++ /dev/null
@@ -1,63 +0,0 @@
-import re
-import glob
-import requests
-import lxml.etree as ET # builtin library doesn't preserve comments
-
-
-SETTINGS_PATH = "app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt"
-START_MARKER = "/* begin language list */"
-END_MARKER = "/* end language list */"
-XML_NAME = "app/src/main/res/values-"
-ISO_MAP_URL = "https://raw.githubusercontent.com/haliaeetus/iso-639/master/data/iso_639-1.min.json"
-INDENT = " "*4
-
-iso_map = requests.get(ISO_MAP_URL, timeout=300).json()
-
-# Load settings file
-src = open(SETTINGS_PATH, "r", encoding='utf-8').read()
-before_src, rest = src.split(START_MARKER)
-rest, after_src = rest.split(END_MARKER)
-
-# Load already added langs
-languages = {}
-for lang in re.finditer(r'Triple\("(.*)", "(.*)", "(.*)"\)', rest):
- flag, name, iso = lang.groups()
- languages[iso] = (flag, name)
-
-# Add not yet added langs
-for folder in glob.glob(f"{XML_NAME}*"):
- iso = folder[len(XML_NAME):]
- if iso not in languages.keys():
- entry = iso_map.get(iso.lower(),{'nativeName':iso})
- languages[iso] = ("", entry['nativeName'].split(',')[0])
-
-# Create triples
-triples = []
-for iso in sorted(languages.keys()):
- flag, name = languages[iso]
- triples.append(f'{INDENT}Triple("{flag}", "{name}", "{iso}"),')
-
-# Update settings file
-open(SETTINGS_PATH, "w+",encoding='utf-8').write(
- before_src +
- START_MARKER +
- "\n" +
- "\n".join(triples) +
- "\n" +
- END_MARKER +
- after_src
-)
-
-# Go through each values.xml file and fix escaped \@string
-for file in glob.glob(f"{XML_NAME}*/strings.xml"):
- try:
- tree = ET.parse(file)
- for child in tree.getroot():
- if child.text.startswith("\\@string/"):
- print(f"[{file}] fixing {child.attrib['name']}")
- child.text = child.text.replace("\\@string/", "@string/")
- with open(file, 'wb') as fp:
- fp.write(b'\n')
- tree.write(fp, encoding="utf-8", method="xml", pretty_print=True, xml_declaration=False)
- except ET.ParseError as ex:
- print(f"[{file}] {ex}")
diff --git a/.github/player.jpg b/.github/player.jpg
new file mode 100644
index 00000000..f6959cf3
Binary files /dev/null and b/.github/player.jpg differ
diff --git a/.github/results.jpg b/.github/results.jpg
new file mode 100644
index 00000000..4dbc9b8d
Binary files /dev/null and b/.github/results.jpg differ
diff --git a/.github/search.jpg b/.github/search.jpg
new file mode 100644
index 00000000..784bec89
Binary files /dev/null and b/.github/search.jpg differ
diff --git a/.github/workflows/build_to_archive.yml b/.github/workflows/build_to_archive.yml
deleted file mode 100644
index 83430766..00000000
--- a/.github/workflows/build_to_archive.yml
+++ /dev/null
@@ -1,76 +0,0 @@
-name: Archive build
-
-on:
- push:
- branches: [ master ]
- paths-ignore:
- - '*.md'
- - '*.json'
- - '**/wcokey.txt'
- workflow_dispatch:
-
-concurrency:
- group: "Archive-build"
- cancel-in-progress: true
-
-jobs:
- build:
- runs-on: ubuntu-latest
- steps:
- - name: Generate access token
- id: generate_token
- uses: tibdex/github-app-token@v1
- with:
- app_id: ${{ secrets.GH_APP_ID }}
- private_key: ${{ secrets.GH_APP_KEY }}
- repository: "recloudstream/secrets"
- - name: Generate access token (archive)
- id: generate_archive_token
- uses: tibdex/github-app-token@v1
- with:
- app_id: ${{ secrets.GH_APP_ID }}
- private_key: ${{ secrets.GH_APP_KEY }}
- repository: "recloudstream/cloudstream-archive"
- - uses: actions/checkout@v2
- - name: Set up JDK 11
- uses: actions/setup-java@v2
- with:
- java-version: '11'
- distribution: 'adopt'
- - name: Grant execute permission for gradlew
- run: chmod +x gradlew
- - name: Fetch keystore
- id: fetch_keystore
- run: |
- TMP_KEYSTORE_FILE_PATH="${RUNNER_TEMP}"/keystore
- mkdir -p "${TMP_KEYSTORE_FILE_PATH}"
- curl -H "Authorization: token ${{ steps.generate_token.outputs.token }}" -o "${TMP_KEYSTORE_FILE_PATH}/prerelease_keystore.keystore" "https://raw.githubusercontent.com/recloudstream/secrets/master/keystore.jks"
- curl -H "Authorization: token ${{ steps.generate_token.outputs.token }}" -o "keystore_password.txt" "https://raw.githubusercontent.com/recloudstream/secrets/master/keystore_password.txt"
- KEY_PWD="$(cat keystore_password.txt)"
- echo "::add-mask::${KEY_PWD}"
- echo "key_pwd=$KEY_PWD" >> $GITHUB_OUTPUT
- - name: Run Gradle
- run: |
- ./gradlew assemblePrerelease
- env:
- SIGNING_KEY_ALIAS: "key0"
- SIGNING_KEY_PASSWORD: ${{ steps.fetch_keystore.outputs.key_pwd }}
- SIGNING_STORE_PASSWORD: ${{ steps.fetch_keystore.outputs.key_pwd }}
- - uses: actions/checkout@v3
- with:
- repository: "recloudstream/cloudstream-archive"
- token: ${{ steps.generate_archive_token.outputs.token }}
- path: "archive"
-
- - name: Move build
- run: |
- cp app/build/outputs/apk/prerelease/release/*.apk "archive/$(git rev-parse --short HEAD).apk"
-
- - name: Push archive
- run: |
- cd $GITHUB_WORKSPACE/archive
- git config --local user.email "actions@github.com"
- git config --local user.name "GitHub Actions"
- git add .
- git commit --amend -m "Build $GITHUB_SHA" || exit 0 # do not error if nothing to commit
- git push --force
\ No newline at end of file
diff --git a/.github/workflows/issue_action.yml b/.github/workflows/issue_action.yml
index 108cec82..79e7766c 100644
--- a/.github/workflows/issue_action.yml
+++ b/.github/workflows/issue_action.yml
@@ -15,28 +15,15 @@ jobs:
app_id: ${{ secrets.GH_APP_ID }}
private_key: ${{ secrets.GH_APP_KEY }}
- name: Similarity analysis
- id: similarity
uses: actions-cool/issues-similarity-analysis@v1
with:
token: ${{ steps.generate_token.outputs.token }}
- filter-threshold: 0.60
+ filter-threshold: 0.5
title-excludes: ''
comment-title: |
### Your issue looks similar to these issues:
Please close if duplicate.
comment-body: '${index}. ${similarity} #${number}'
- - name: Label if possible duplicate
- if: steps.similarity.outputs.similar-issues-found =='true'
- uses: actions/github-script@v6
- with:
- github-token: ${{ steps.generate_token.outputs.token }}
- script: |
- github.rest.issues.addLabels({
- issue_number: context.issue.number,
- owner: context.repo.owner,
- repo: context.repo.repo,
- labels: ["possible duplicate"]
- })
- uses: actions/checkout@v2
- name: Automatically close issues that dont follow the issue template
uses: lucasbento/auto-close-issues@v1.0.2
@@ -54,7 +41,7 @@ jobs:
wget --output-document check_issue.py "https://raw.githubusercontent.com/recloudstream/.github/master/.github/check_issue.py"
pip3 install httpx
RES="$(python3 ./check_issue.py)"
- echo "name=${RES}" >> $GITHUB_OUTPUT
+ echo "::set-output name=name::${RES}"
- name: Comment if issue mentions a provider
if: steps.provider_check.outputs.name != 'none'
uses: actions-cool/issues-helper@v3
@@ -66,18 +53,6 @@ jobs:
Please do not report any provider bugs here. This repository does not contain any providers. Please find the appropriate repository and report your issue there or join the [discord](https://discord.gg/5Hus6fM).
Found provider name: `${{ steps.provider_check.outputs.name }}`
- - name: Label if mentions provider
- if: steps.provider_check.outputs.name != 'none'
- uses: actions/github-script@v6
- with:
- github-token: ${{ steps.generate_token.outputs.token }}
- script: |
- github.rest.issues.addLabels({
- issue_number: context.issue.number,
- owner: context.repo.owner,
- repo: context.repo.repo,
- labels: ["possible provider issue"]
- })
- name: Add eyes reaction to all issues
uses: actions-cool/emoji-helper@v1.0.0
with:
diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml
index 4ce7dba1..37161d6b 100644
--- a/.github/workflows/prerelease.yml
+++ b/.github/workflows/prerelease.yml
@@ -40,7 +40,7 @@ jobs:
curl -H "Authorization: token ${{ steps.generate_token.outputs.token }}" -o "keystore_password.txt" "https://raw.githubusercontent.com/recloudstream/secrets/master/keystore_password.txt"
KEY_PWD="$(cat keystore_password.txt)"
echo "::add-mask::${KEY_PWD}"
- echo "key_pwd=$KEY_PWD" >> $GITHUB_OUTPUT
+ echo "::set-output name=key_pwd::$KEY_PWD"
- name: Run Gradle
run: |
./gradlew assemblePrerelease makeJar androidSourcesJar
@@ -56,6 +56,6 @@ jobs:
prerelease: true
title: "Pre-release Build"
files: |
- app/build/outputs/apk/prerelease/release/*.apk
+ app/build/outputs/apk/prerelease/*.apk
app/build/libs/app-sources.jar
app/build/classes.jar
diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml
index 36199cd6..1a4db134 100644
--- a/.github/workflows/pull_request.yml
+++ b/.github/workflows/pull_request.yml
@@ -15,9 +15,9 @@ jobs:
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Run Gradle
- run: ./gradlew assemblePrereleaseDebug
+ run: ./gradlew assembleDebug
- name: Upload Artifact
uses: actions/upload-artifact@v2
with:
name: pull-request-build
- path: "app/build/outputs/apk/prerelease/debug/*.apk"
+ path: "app/build/outputs/apk/debug/*.apk"
diff --git a/.github/workflows/update_locales.yml b/.github/workflows/update_locales.yml
deleted file mode 100644
index 628e9bc9..00000000
--- a/.github/workflows/update_locales.yml
+++ /dev/null
@@ -1,42 +0,0 @@
-name: Fix locale issues
-
-on:
- workflow_dispatch:
- push:
- paths:
- - '**.xml'
- branches:
- - master
-
-concurrency:
- group: "locale"
- cancel-in-progress: true
-
-jobs:
- create:
- runs-on: ubuntu-latest
- steps:
- - name: Generate access token
- id: generate_token
- uses: tibdex/github-app-token@v1
- with:
- app_id: ${{ secrets.GH_APP_ID }}
- private_key: ${{ secrets.GH_APP_KEY }}
- repository: "recloudstream/cloudstream"
- - uses: actions/checkout@v2
- with:
- token: ${{ steps.generate_token.outputs.token }}
- - name: Install dependencies
- run: |
- pip3 install lxml
- - name: Edit files
- run: |
- python3 .github/locales.py
- - name: Commit to the repo
- run: |
- git config --local user.email "111277985+recloudstream[bot]@users.noreply.github.com"
- git config --local user.name "recloudstream[bot]"
- git add .
- # "echo" returns true so the build succeeds, even if no changed files
- git commit -m 'chore(locales): fix locale issues' || echo
- git push
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
index 333d4937..652d9f3f 100644
--- a/.idea/jarRepositories.xml
+++ b/.idea/jarRepositories.xml
@@ -31,10 +31,5 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/README.md b/README.md
index e3d033ba..1c9ca358 100644
--- a/README.md
+++ b/README.md
@@ -1,18 +1,45 @@
# CloudStream
**⚠️ Warning: By default this app doesn't provide any video sources, you have to install extensions in order to add functionality to the app.**
+You can find the list of community-maintained extension repositories [here
+](https://recloudstream.github.io/repos/)
[](https://discord.gg/5Hus6fM)
-### Features:
+***Features:***
+ **AdFree**, No ads whatsoever
+ No tracking/analytics
+ Bookmarks
+ Download and stream movies, tv-shows and anime
+ Chromecast
-### Supported languages:
-
-
-
+***Screenshots:***
+
+


+
+
+***The list of supported languages:***
+* 🇱🇧 Arabic
+* 🇭🇷 Croatian
+* 🇨🇿 Czech
+* 🇳🇱 Dutch
+* 🇬🇧 English
+* 🇫🇷 French
+* 🇩🇪 German
+* 🇬🇷 Greek
+* 🇮🇳 Hindi
+* 🇮🇩 Indonesian
+* 🇮🇹 Italian
+* 🇲🇰 Macedonian
+* 🇮🇳 Malayalam
+* 🇳🇴 Norsk
+* 🇵🇱 Polish
+* 🇧🇷 Portuguese (Brazil)
+* 🇷🇴 Romanian
+* 🇪🇸 Spanish
+* 🇸🇪 Swedish
+* 🇵🇭 Tagalog
+* 🇹🇷 Turkish
+* 🇻🇳 Vietnamese
+
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 00000000..291e71ff
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,233 @@
+plugins {
+ id 'com.android.application'
+ id 'kotlin-android'
+ id 'kotlin-kapt'
+ id 'kotlin-android-extensions'
+ id 'org.jetbrains.dokka'
+}
+
+def tmpFilePath = System.getProperty("user.home") + "/work/_temp/keystore/"
+def allFilesFromDir = new File(tmpFilePath).listFiles()
+def prereleaseStoreFile = null
+if (allFilesFromDir != null) {
+ prereleaseStoreFile = allFilesFromDir.first()
+}
+
+android {
+ testOptions {
+ unitTests.returnDefaultValues = true
+ }
+ signingConfigs {
+ prerelease {
+ if (prereleaseStoreFile != null) {
+ storeFile = file(prereleaseStoreFile)
+ storePassword System.getenv("SIGNING_STORE_PASSWORD")
+ keyAlias System.getenv("SIGNING_KEY_ALIAS")
+ keyPassword System.getenv("SIGNING_KEY_PASSWORD")
+ }
+ }
+ }
+ compileSdkVersion 31
+ buildToolsVersion "30.0.3"
+
+ defaultConfig {
+ applicationId "com.lagradost.cloudstream3"
+ minSdkVersion 21
+ targetSdkVersion 30
+
+ versionCode 51
+ versionName "3.1.5"
+
+ resValue "string", "app_version",
+ "${defaultConfig.versionName}${versionNameSuffix ?: ""}"
+
+ resValue "string", "commit_hash",
+ ("git rev-parse --short HEAD".execute().text.trim() ?: "")
+
+ resValue "bool", "is_prerelease", "false"
+
+ buildConfigField("String", "BUILDDATE", "new java.text.SimpleDateFormat(\"yyyy-MM-dd HH:mm\").format(new java.util.Date(" + System.currentTimeMillis() + "L));")
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+
+ kapt {
+ includeCompileClasspath = true
+ }
+ }
+
+ buildTypes {
+ // release {
+ // debuggable false
+ // minifyEnabled false
+ // shrinkResources false
+ // proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ // resValue "bool", "is_prerelease", "false"
+ // }
+ prerelease {
+ applicationIdSuffix ".prerelease"
+ buildConfigField("boolean", "BETA", "true")
+ signingConfig signingConfigs.prerelease
+ versionNameSuffix '-PRE'
+ debuggable false
+ minifyEnabled false
+ shrinkResources false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ resValue "bool", "is_prerelease", "true"
+ }
+ debug {
+ debuggable true
+ applicationIdSuffix ".debug"
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ resValue "bool", "is_prerelease", "true"
+ }
+ }
+ compileOptions {
+ coreLibraryDesugaringEnabled true
+
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ freeCompilerArgs = ['-Xjvm-default=compatibility']
+ }
+ lintOptions {
+ checkReleaseBuilds false
+ abortOnError false
+ }
+}
+
+repositories {
+ maven { url 'https://jitpack.io' }
+}
+
+dependencies {
+ implementation 'com.google.android.mediahome:video:1.0.0'
+ implementation 'androidx.test.ext:junit-ktx:1.1.3'
+ testImplementation 'org.json:json:20180813'
+
+ implementation 'androidx.core:core-ktx:1.8.0'
+ implementation 'androidx.appcompat:appcompat:1.4.2' // need target 32 for 1.5.0
+
+ // dont change this to 1.6.0 it looks ugly af
+ implementation 'com.google.android.material:material:1.5.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ implementation 'androidx.navigation:navigation-fragment-ktx:2.5.1'
+ implementation 'androidx.navigation:navigation-ui-ktx:2.5.1'
+ implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.1'
+ implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.3'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
+
+ //implementation "io.karn:khttp-android:0.1.2" //okhttp instead
+// implementation 'org.jsoup:jsoup:1.13.1'
+ implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.13.1"
+
+ implementation "androidx.preference:preference-ktx:1.2.0"
+
+ implementation 'com.github.bumptech.glide:glide:4.13.1'
+ kapt 'com.github.bumptech.glide:compiler:4.13.1'
+ implementation 'com.github.bumptech.glide:okhttp3-integration:4.13.0'
+
+ implementation 'jp.wasabeef:glide-transformations:4.3.0'
+
+ implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
+
+ // implementation "androidx.leanback:leanback-paging:1.1.0-alpha09"
+
+ // Exoplayer
+ implementation 'com.google.android.exoplayer:exoplayer:2.16.1'
+ implementation 'com.google.android.exoplayer:extension-cast:2.16.1'
+ implementation "com.google.android.exoplayer:extension-mediasession:2.16.1"
+ implementation 'com.google.android.exoplayer:extension-okhttp:2.16.1'
+
+ //implementation "com.google.android.exoplayer:extension-leanback:2.14.0"
+
+ // Bug reports
+ implementation "ch.acra:acra-core:5.8.4"
+ implementation "ch.acra:acra-toast:5.8.4"
+
+ compileOnly "com.google.auto.service:auto-service-annotations:1.0"
+ //either for java sources:
+ annotationProcessor "com.google.auto.service:auto-service:1.0"
+ //or for kotlin sources (requires kapt gradle plugin):
+ kapt "com.google.auto.service:auto-service:1.0"
+
+ // subtitle color picker
+ implementation 'com.jaredrummler:colorpicker:1.1.0'
+
+ //run JS
+ implementation 'org.mozilla:rhino:1.7.14'
+
+ // TorrentStream
+ //implementation 'com.github.TorrentStream:TorrentStream-Android:2.7.0'
+
+ // Downloading
+ implementation "androidx.work:work-runtime:2.7.1"
+ implementation "androidx.work:work-runtime-ktx:2.7.1"
+
+ // Networking
+// implementation "com.squareup.okhttp3:okhttp:4.9.2"
+// implementation "com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.1"
+ implementation 'com.github.Blatzar:NiceHttp:0.3.3'
+
+ // Util to skip the URI file fuckery 🙏
+ implementation "com.github.tachiyomiorg:unifile:17bec43"
+
+ // API because cba maintaining it myself
+ implementation "com.uwetrottmann.tmdb2:tmdb-java:2.6.0"
+
+ implementation 'com.github.discord:OverlappingPanels:0.1.3'
+ // debugImplementation because LeakCanary should only run in debug builds.
+ // debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
+
+ // for shimmer when loading
+ implementation 'com.facebook.shimmer:shimmer:0.5.0'
+
+ implementation "androidx.tvprovider:tvprovider:1.0.0"
+
+ // used for subtitle decoding https://github.com/albfernandez/juniversalchardet
+ implementation 'com.github.albfernandez:juniversalchardet:2.4.0'
+
+ // slow af yt
+ //implementation 'com.github.HaarigerHarald:android-youtubeExtractor:master-SNAPSHOT'
+
+ // newpipe yt
+ implementation 'com.github.recloudstream:NewPipeExtractor:master-SNAPSHOT'
+ coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
+
+ // Library/extensions searching with Levenshtein distance
+ implementation 'me.xdrop:fuzzywuzzy:1.4.0'
+}
+
+task androidSourcesJar(type: Jar) {
+ getArchiveClassifier().set('sources')
+ from android.sourceSets.main.java.srcDirs//full sources
+}
+
+// this is used by the gradlew plugin
+task makeJar(type: Copy) {
+ from('build/intermediates/compile_app_classes_jar/debug')
+ into('build')
+ include('classes.jar')
+ dependsOn('build')
+}
+
+dokkaHtml {
+ moduleName.set("Cloudstream")
+ dokkaSourceSets {
+ main {
+ sourceLink {
+ // Unix based directory relative path to the root of the project (where you execute gradle respectively).
+ localDirectory.set(file("src/main/java"))
+
+ // URL showing where the source code can be accessed through the web browser
+ remoteUrl.set(new URL(
+ "https://github.com/recloudstream/cloudstream/tree/master/app/src/main/java"))
+ // Suffix which is used to append the line number to the URL. Use #L for GitHub
+ remoteLineSuffix.set("#L")
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
deleted file mode 100644
index 0bd56fe7..00000000
--- a/app/build.gradle.kts
+++ /dev/null
@@ -1,258 +0,0 @@
-import com.android.build.gradle.api.BaseVariantOutput
-import org.jetbrains.dokka.gradle.DokkaTask
-import java.io.ByteArrayOutputStream
-import java.net.URL
-
-plugins {
- id("com.android.application")
- id("kotlin-android")
- id("kotlin-kapt")
- id("kotlin-android-extensions")
- id("org.jetbrains.dokka")
-}
-
-val tmpFilePath = System.getProperty("user.home") + "/work/_temp/keystore/"
-val prereleaseStoreFile: File? = File(tmpFilePath).listFiles()?.first()
-
-fun String.execute() = ByteArrayOutputStream().use { baot ->
- if (project.exec {
- workingDir = projectDir
- commandLine = this@execute.split(Regex("\\s"))
- standardOutput = baot
- }.exitValue == 0)
- String(baot.toByteArray()).trim()
- else null
-}
-
-android {
- testOptions {
- unitTests.isReturnDefaultValues = true
- }
- signingConfigs {
- create("prerelease") {
- if (prereleaseStoreFile != null) {
- storeFile = file(prereleaseStoreFile)
- storePassword = System.getenv("SIGNING_STORE_PASSWORD")
- keyAlias = System.getenv("SIGNING_KEY_ALIAS")
- keyPassword = System.getenv("SIGNING_KEY_PASSWORD")
- }
- }
- }
-
- compileSdk = 33
- buildToolsVersion = "30.0.3"
-
- defaultConfig {
- applicationId = "com.lagradost.cloudstream3"
- minSdk = 21
- targetSdk = 33
-
- versionCode = 57
- versionName = "4.0.0"
-
- resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}")
-
- resValue("string", "commit_hash", "git rev-parse --short HEAD".execute() ?: "")
-
- resValue("bool", "is_prerelease", "false")
-
- buildConfigField(
- "String",
- "BUILDDATE",
- "new java.text.SimpleDateFormat(\"yyyy-MM-dd HH:mm\").format(new java.util.Date(" + System.currentTimeMillis() + "L));"
- )
-
- testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
-
- kapt {
- includeCompileClasspath = true
- }
- }
-
- buildTypes {
- release {
- isDebuggable = false
- isMinifyEnabled = false
- isShrinkResources = false
- proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
- }
- debug {
- isDebuggable = true
- applicationIdSuffix = ".debug"
- proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
- }
- }
- flavorDimensions.add("state")
- productFlavors {
- create("stable") {
- dimension = "state"
- resValue("bool", "is_prerelease", "false")
- }
- create("prerelease") {
- dimension = "state"
- resValue("bool", "is_prerelease", "true")
- buildConfigField("boolean", "BETA", "true")
- applicationIdSuffix = ".prerelease"
- signingConfig = signingConfigs.getByName("prerelease")
- versionNameSuffix = "-PRE"
- versionCode = (System.currentTimeMillis() / 60000).toInt()
- }
- }
- compileOptions {
- isCoreLibraryDesugaringEnabled = true
-
- sourceCompatibility = JavaVersion.VERSION_1_8
- targetCompatibility = JavaVersion.VERSION_1_8
- }
- kotlinOptions {
- jvmTarget = "1.8"
- freeCompilerArgs = listOf("-Xjvm-default=compatibility")
- }
- lint {
- abortOnError = false
- checkReleaseBuilds = false
- }
- namespace = "com.lagradost.cloudstream3"
-}
-
-repositories {
- maven("https://jitpack.io")
-}
-
-dependencies {
- implementation("com.google.android.mediahome:video:1.0.0")
- implementation("androidx.test.ext:junit-ktx:1.1.3")
- testImplementation("org.json:json:20180813")
-
- implementation("androidx.core:core-ktx:1.8.0")
- implementation("androidx.appcompat:appcompat:1.4.2") // need target 32 for 1.5.0
-
- // dont change this to 1.6.0 it looks ugly af
- implementation("com.google.android.material:material:1.5.0")
- implementation("androidx.constraintlayout:constraintlayout:2.1.4")
- implementation("androidx.navigation:navigation-fragment-ktx:2.5.1")
- implementation("androidx.navigation:navigation-ui-ktx:2.5.1")
- implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.5.1")
- implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1")
- testImplementation("junit:junit:4.13.2")
- androidTestImplementation("androidx.test.ext:junit:1.1.3")
- androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
-
- //implementation("io.karn:khttp-android:0.1.2") //okhttp instead
-// implementation("org.jsoup:jsoup:1.13.1")
- implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.13.1")
-
- implementation("androidx.preference:preference-ktx:1.2.0")
-
- implementation("com.github.bumptech.glide:glide:4.13.1")
- kapt("com.github.bumptech.glide:compiler:4.13.1")
- implementation("com.github.bumptech.glide:okhttp3-integration:4.13.0")
-
- implementation("jp.wasabeef:glide-transformations:4.3.0")
-
- implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
-
- // implementation("androidx.leanback:leanback-paging:1.1.0-alpha09")
-
- // Exoplayer
- implementation("com.google.android.exoplayer:exoplayer:2.18.2")
- implementation("com.google.android.exoplayer:extension-cast:2.18.2")
- implementation("com.google.android.exoplayer:extension-mediasession:2.18.2")
- implementation("com.google.android.exoplayer:extension-okhttp:2.18.2")
- // Use the Jellyfin ffmpeg extension for easy ffmpeg audio decoding in exoplayer. Thank you Jellyfin <3
-// implementation("org.jellyfin.exoplayer:exoplayer-ffmpeg-extension:2.18.2+1")
-
- //implementation("com.google.android.exoplayer:extension-leanback:2.14.0")
-
- // Bug reports
- implementation("ch.acra:acra-core:5.8.4")
- implementation("ch.acra:acra-toast:5.8.4")
-
- compileOnly("com.google.auto.service:auto-service-annotations:1.0")
- //either for java sources:
- annotationProcessor("com.google.auto.service:auto-service:1.0")
- //or for kotlin sources (requires kapt gradle plugin):
- kapt("com.google.auto.service:auto-service:1.0")
-
- // subtitle color picker
- implementation("com.jaredrummler:colorpicker:1.1.0")
-
- //run JS
- // do not upgrade to 1.7.14, since in 1.7.14 Rhino uses the `SourceVersion` class, which is not
- // available on Android (even when using desugaring), and `NoClassDefFoundError` is thrown
- implementation("org.mozilla:rhino:1.7.13")
-
- // TorrentStream
- //implementation("com.github.TorrentStream:TorrentStream-Android:2.7.0")
-
- // Downloading
- implementation("androidx.work:work-runtime:2.8.0")
- implementation("androidx.work:work-runtime-ktx:2.8.0")
-
- // Networking
-// implementation("com.squareup.okhttp3:okhttp:4.9.2")
-// implementation("com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.1")
- implementation("com.github.Blatzar:NiceHttp:0.4.2")
- // To fix SSL fuckery on android 9
- implementation("org.conscrypt:conscrypt-android:2.2.1")
- // Util to skip the URI file fuckery 🙏
- implementation("com.github.tachiyomiorg:unifile:17bec43")
-
- // API because cba maintaining it myself
- implementation("com.uwetrottmann.tmdb2:tmdb-java:2.6.0")
-
- implementation("com.github.discord:OverlappingPanels:0.1.3")
- // debugImplementation because LeakCanary should only run in debug builds.
- // debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
-
- // for shimmer when loading
- implementation("com.facebook.shimmer:shimmer:0.5.0")
-
- implementation("androidx.tvprovider:tvprovider:1.0.0")
-
- // used for subtitle decoding https://github.com/albfernandez/juniversalchardet
- implementation("com.github.albfernandez:juniversalchardet:2.4.0")
-
- // slow af yt
- //implementation("com.github.HaarigerHarald:android-youtubeExtractor:master-SNAPSHOT")
-
- // newpipe yt taken from https://github.com/TeamNewPipe/NewPipe/blob/dev/app/build.gradle#L190
- implementation("com.github.TeamNewPipe:NewPipeExtractor:9ffdd0948b2ecd82655f5ff2a3e127b2b7695d5b")
- coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.6")
-
- // Library/extensions searching with Levenshtein distance
- implementation("me.xdrop:fuzzywuzzy:1.4.0")
-
- // color pallette for images -> colors
- implementation("androidx.palette:palette-ktx:1.0.0")
-}
-
-tasks.register("androidSourcesJar", Jar::class) {
- archiveClassifier.set("sources")
- from(android.sourceSets.getByName("main").java.srcDirs) //full sources
-}
-
-// this is used by the gradlew plugin
-tasks.register("makeJar", Copy::class) {
- from("build/intermediates/compile_app_classes_jar/prereleaseDebug")
- into("build")
- include("classes.jar")
- dependsOn("build")
-}
-
-tasks.withType().configureEach {
- moduleName.set("Cloudstream")
- dokkaSourceSets {
- named("main") {
- sourceLink {
- // Unix based directory relative path to the root of the project (where you execute gradle respectively).
- localDirectory.set(file("src/main/java"))
-
- // URL showing where the source code can be accessed through the web browser
- remoteUrl.set(URL("https://github.com/recloudstream/cloudstream/tree/master/app/src/main/java"))
- // Suffix which is used to append the line number to the URL. Use #L for GitHub
- remoteLineSuffix.set("#L")
- }
- }
- }
-}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
index ff59496d..481bb434 100644
--- a/app/proguard-rules.pro
+++ b/app/proguard-rules.pro
@@ -1,6 +1,6 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
-# proguardFiles setting in build.gradle.kts.
+# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
diff --git a/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt
index 92042d60..201ddea3 100644
--- a/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt
+++ b/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt
@@ -1,8 +1,9 @@
package com.lagradost.cloudstream3
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.lagradost.cloudstream3.mvvm.logError
+import com.lagradost.cloudstream3.utils.Qualities
import com.lagradost.cloudstream3.utils.SubtitleHelper
-import com.lagradost.cloudstream3.utils.TestingUtils
import kotlinx.coroutines.runBlocking
import org.junit.Assert
import org.junit.Test
@@ -15,11 +16,142 @@ import org.junit.runner.RunWith
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
+ //@Test
+ //fun useAppContext() {
+ // // Context of the app under test.
+ // val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ // assertEquals("com.lagradost.cloudstream3", appContext.packageName)
+ //}
+
private fun getAllProviders(): List {
- println("Providers: ${APIHolder.allProviders.size}")
return APIHolder.allProviders //.filter { !it.usesWebView }
}
+ private suspend fun loadLinks(api: MainAPI, url: String?): Boolean {
+ Assert.assertNotNull("Api ${api.name} has invalid url on episode", url)
+ if (url == null) return true
+ var linksLoaded = 0
+ try {
+ val success = api.loadLinks(url, false, {}) { link ->
+ Assert.assertTrue(
+ "Api ${api.name} returns link with invalid Quality",
+ Qualities.values().map { it.value }.contains(link.quality)
+ )
+ Assert.assertTrue(
+ "Api ${api.name} returns link with invalid url ${link.url}",
+ link.url.length > 4
+ )
+ linksLoaded++
+ }
+ if (success) {
+ return linksLoaded > 0
+ }
+ Assert.assertTrue("Api ${api.name} has returns false on .loadLinks", success)
+ } catch (e: Exception) {
+ if (e.cause is NotImplementedError) {
+ Assert.fail("Provider has not implemented .loadLinks")
+ }
+ logError(e)
+ }
+ return true
+ }
+
+ private suspend fun testSingleProviderApi(api: MainAPI): Boolean {
+ val searchQueries = listOf("over", "iron", "guy")
+ var correctResponses = 0
+ var searchResult: List? = null
+ for (query in searchQueries) {
+ val response = try {
+ api.search(query)
+ } catch (e: Exception) {
+ if (e.cause is NotImplementedError) {
+ Assert.fail("Provider has not implemented .search")
+ }
+ logError(e)
+ null
+ }
+ if (!response.isNullOrEmpty()) {
+ correctResponses++
+ if (searchResult == null) {
+ searchResult = response
+ }
+ }
+ }
+
+ if (correctResponses == 0 || searchResult == null) {
+ System.err.println("Api ${api.name} did not return any valid search responses")
+ return false
+ }
+
+ try {
+ var validResults = false
+ for (result in searchResult) {
+ Assert.assertEquals(
+ "Invalid apiName on response on ${api.name}",
+ result.apiName,
+ api.name
+ )
+ val load = api.load(result.url) ?: continue
+ Assert.assertEquals(
+ "Invalid apiName on load on ${api.name}",
+ load.apiName,
+ result.apiName
+ )
+ Assert.assertTrue(
+ "Api ${api.name} on load does not contain any of the supportedTypes",
+ api.supportedTypes.contains(load.type)
+ )
+ when (load) {
+ is AnimeLoadResponse -> {
+ val gotNoEpisodes =
+ load.episodes.keys.isEmpty() || load.episodes.keys.any { load.episodes[it].isNullOrEmpty() }
+
+ if (gotNoEpisodes) {
+ println("Api ${api.name} got no episodes on ${load.url}")
+ continue
+ }
+
+ val url = (load.episodes[load.episodes.keys.first()])?.first()?.data
+ validResults = loadLinks(api, url)
+ if (!validResults) continue
+ }
+ is MovieLoadResponse -> {
+ val gotNoEpisodes = load.dataUrl.isBlank()
+ if (gotNoEpisodes) {
+ println("Api ${api.name} got no movie on ${load.url}")
+ continue
+ }
+
+ validResults = loadLinks(api, load.dataUrl)
+ if (!validResults) continue
+ }
+ is TvSeriesLoadResponse -> {
+ val gotNoEpisodes = load.episodes.isEmpty()
+ if (gotNoEpisodes) {
+ println("Api ${api.name} got no episodes on ${load.url}")
+ continue
+ }
+
+ validResults = loadLinks(api, load.episodes.first().data)
+ if (!validResults) continue
+ }
+ }
+ break
+ }
+ if(!validResults) {
+ System.err.println("Api ${api.name} did not load on any")
+ }
+
+ return validResults
+ } catch (e: Exception) {
+ if (e.cause is NotImplementedError) {
+ Assert.fail("Provider has not implemented .load")
+ }
+ logError(e)
+ return false
+ }
+ }
+
@Test
fun providersExist() {
Assert.assertTrue(getAllProviders().isNotEmpty())
@@ -27,7 +159,6 @@ class ExampleInstrumentedTest {
}
@Test
- @Throws(AssertionError::class)
fun providerCorrectData() {
val isoNames = SubtitleHelper.languages.map { it.ISO_639_1 }
Assert.assertFalse("ISO does not contain any languages", isoNames.isNullOrEmpty())
@@ -49,21 +180,66 @@ class ExampleInstrumentedTest {
@Test
fun providerCorrectHomepage() {
runBlocking {
- getAllProviders().amap { api ->
- TestingUtils.testHomepage(api, ::println)
+ getAllProviders().apmap { api ->
+ if (api.hasMainPage) {
+ try {
+ val homepage = api.getMainPage()
+ when {
+ homepage == null -> {
+ System.err.println("Homepage provider ${api.name} did not correctly load homepage!")
+ }
+ homepage.items.isEmpty() -> {
+ System.err.println("Homepage provider ${api.name} does not contain any items!")
+ }
+ homepage.items.any { it.list.isEmpty() } -> {
+ System.err.println ("Homepage provider ${api.name} does not have any items on result!")
+ }
+ }
+ } catch (e: Exception) {
+ if (e.cause is NotImplementedError) {
+ Assert.fail("Provider marked as hasMainPage, while in reality is has not been implemented")
+ }
+ logError(e)
+ }
+ }
}
}
println("Done providerCorrectHomepage")
}
+// @Test
+// fun testSingleProvider() {
+// testSingleProviderApi(ThenosProvider())
+// }
+
@Test
- fun testAllProvidersCorrect() {
+ fun providerCorrect() {
runBlocking {
- TestingUtils.getDeferredProviderTests(
- this,
- getAllProviders(),
- ::println
- ) { _, _ -> }
+ val invalidProvider = ArrayList>()
+ val providers = getAllProviders()
+ providers.apmap { api ->
+ try {
+ println("Trying $api")
+ if (testSingleProviderApi(api)) {
+ println("Success $api")
+ } else {
+ System.err.println("Error $api")
+ invalidProvider.add(Pair(api, null))
+ }
+ } catch (e: Exception) {
+ logError(e)
+ invalidProvider.add(Pair(api, e))
+ }
+ }
+ if(invalidProvider.isEmpty()) {
+ println("No Invalid providers! :D")
+ } else {
+ println("Invalid providers are: ")
+ for (provider in invalidProvider) {
+ println("${provider.first}")
+ }
+ }
}
+ println("Done providerCorrect")
}
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 563c82f8..216a5f21 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,6 +1,7 @@
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.lagradost.cloudstream3">
@@ -10,11 +11,7 @@
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
@@ -124,30 +109,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -183,10 +144,6 @@
android:name=".ui.ControllerActivity"
android:exported="false" />
-
-
Unit)) :
ACRA.errorReporter.handleException(error)
try {
PrintStream(errorFile).use { ps ->
+ ps.println(String.format("Enabled resource pack: ${ResourcePackManager.activePackId ?: "none"}"))
ps.println(String.format("Currently loading extension: ${PluginManager.currentlyLoading ?: "none"}"))
ps.println(
String.format(
diff --git a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt
index 89f0ae51..32df314f 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt
@@ -1,6 +1,5 @@
package com.lagradost.cloudstream3
-import android.Manifest
import android.app.Activity
import android.app.PictureInPictureParams
import android.content.Context
@@ -17,7 +16,6 @@ import androidx.annotation.MainThread
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SearchView
-import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager
import com.google.android.gms.cast.framework.CastSession
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
@@ -63,9 +61,7 @@ object CommonActivity {
}
}
- /** duration is Toast.LENGTH_SHORT if null*/
- @MainThread
- fun showToast(act: Activity?, @StringRes message: Int, duration: Int? = null) {
+ fun showToast(act: Activity?, @StringRes message: Int, duration: Int) {
if (act == null) return
showToast(act, act.getString(message), duration)
}
@@ -73,7 +69,6 @@ object CommonActivity {
const val TAG = "COMPACT"
/** duration is Toast.LENGTH_SHORT if null*/
- @MainThread
fun showToast(act: Activity?, message: String?, duration: Int? = null) {
if (act == null || message == null) {
Log.w(TAG, "invalid showToast act = $act message = $message")
@@ -110,18 +105,9 @@ object CommonActivity {
}
}
- /**
- * Not all languages can be fetched from locale with a code.
- * This map allows sidestepping the default Locale(languageCode)
- * when setting the app language.
- **/
- val appLanguageExceptions = hashMapOf(
- "zh-rTW" to Locale.TRADITIONAL_CHINESE
- )
-
fun setLocale(context: Context?, languageCode: String?) {
if (context == null || languageCode == null) return
- val locale = appLanguageExceptions[languageCode] ?: Locale(languageCode)
+ val locale = Locale(languageCode)
val resources: Resources = context.resources
val config = resources.configuration
Locale.setDefault(locale)
@@ -157,8 +143,8 @@ object CommonActivity {
val resultCode = result.resultCode
val data = result.data
if (resultCode == AppCompatActivity.RESULT_OK && data != null && resumeApp.position != null && resumeApp.duration != null) {
- val pos = resumeApp.getPosition(data)
- val dur = resumeApp.getDuration(data)
+ val pos = data.getLongExtra(resumeApp.position, -1L)
+ val dur = data.getLongExtra(resumeApp.duration, -1L)
if (dur > 0L && pos > 0L)
DataStoreHelper.setViewPos(getKey(resumeApp.lastId), pos, dur)
removeKey(resumeApp.lastId)
@@ -166,23 +152,6 @@ object CommonActivity {
}
}
}
-
- // Ask for notification permissions on Android 13
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
- ContextCompat.checkSelfPermission(
- act,
- Manifest.permission.POST_NOTIFICATIONS
- ) != PackageManager.PERMISSION_GRANTED
- ) {
- val requestPermissionLauncher = act.registerForActivityResult(
- ActivityResultContracts.RequestPermission()
- ) { isGranted: Boolean ->
- Log.d(TAG, "Notification permission: $isGranted")
- }
- requestPermissionLauncher.launch(
- Manifest.permission.POST_NOTIFICATIONS
- )
- }
}
private fun Activity.enterPIPMode() {
@@ -368,9 +337,6 @@ object CommonActivity {
KeyEvent.KEYCODE_C, KeyEvent.KEYCODE_NUMPAD_4, KeyEvent.KEYCODE_4 -> {
PlayerEventType.SkipOp
}
- KeyEvent.KEYCODE_V, KeyEvent.KEYCODE_NUMPAD_5, KeyEvent.KEYCODE_5 -> {
- PlayerEventType.SkipCurrentChapter
- }
KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, KeyEvent.KEYCODE_P, KeyEvent.KEYCODE_SPACE, KeyEvent.KEYCODE_NUMPAD_ENTER, KeyEvent.KEYCODE_ENTER -> { // space is not captured due to navigation
PlayerEventType.PlayPauseToggle
}
@@ -449,4 +415,4 @@ object CommonActivity {
}
return null
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/HeaderDecorationBindingAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/HeaderDecorationBindingAdapter.kt
deleted file mode 100644
index 045a7963..00000000
--- a/app/src/main/java/com/lagradost/cloudstream3/HeaderDecorationBindingAdapter.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.lagradost.cloudstream3
-
-import android.view.LayoutInflater
-import androidx.annotation.LayoutRes
-import androidx.recyclerview.widget.RecyclerView
-import com.lagradost.cloudstream3.ui.HeaderViewDecoration
-
-fun setHeaderDecoration(view: RecyclerView, @LayoutRes headerViewRes: Int) {
- val headerView = LayoutInflater.from(view.context).inflate(headerViewRes, null)
- view.addItemDecoration(HeaderViewDecoration(headerView))
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt
index c20786c3..47afbc42 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt
@@ -13,17 +13,17 @@ import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi
-import com.lagradost.cloudstream3.syncproviders.SyncIdName
import com.lagradost.cloudstream3.ui.player.SubtitleData
-import com.lagradost.cloudstream3.utils.*
+import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.utils.AppUtils.toJson
-import com.lagradost.cloudstream3.utils.Coroutines.mainWork
-import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf
+import com.lagradost.cloudstream3.utils.ExtractorLink
+import com.lagradost.cloudstream3.utils.Qualities
+import com.lagradost.cloudstream3.utils.loadExtractor
import okhttp3.Interceptor
-import org.mozilla.javascript.Scriptable
import java.text.SimpleDateFormat
import java.util.*
import kotlin.math.absoluteValue
+import kotlin.collections.MutableList
const val USER_AGENT =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
@@ -32,12 +32,6 @@ const val USER_AGENT =
val mapper = JsonMapper.builder().addModule(KotlinModule())
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()!!
-/**
- * Defines the constant for the all languages preference, if this is set then it is
- * the equivalent of all languages being set
- **/
-const val AllLanguagesName = "universal"
-
object APIHolder {
val unixTime: Long
get() = System.currentTimeMillis() / 1000L
@@ -47,7 +41,7 @@ object APIHolder {
private const val defProvider = 0
// ConcurrentModificationException is possible!!!
- val allProviders = threadSafeListOf()
+ val allProviders: MutableList = arrayListOf()
fun initAll() {
for (api in allProviders) {
@@ -60,7 +54,7 @@ object APIHolder {
return this.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }
}
- var apis: List = threadSafeListOf()
+ var apis: List = arrayListOf()
var apiMap: Map? = null
fun addPluginMapping(plugin: MainAPI) {
@@ -80,20 +74,16 @@ object APIHolder {
fun getApiFromNameNull(apiName: String?): MainAPI? {
if (apiName == null) return null
- synchronized(allProviders) {
- initMap()
- return apiMap?.get(apiName)?.let { apis.getOrNull(it) }
- // Leave the ?. null check, it can crash regardless
- ?: allProviders.firstOrNull { it.name == apiName }
- }
+ initMap()
+ return apiMap?.get(apiName)?.let { apis.getOrNull(it) }
+ ?: allProviders.firstOrNull { it.name == apiName }
}
fun getApiFromUrlNull(url: String?): MainAPI? {
if (url == null) return null
- synchronized(allProviders) {
- allProviders.forEach { api ->
- if (url.startsWith(api.mainUrl)) return api
- }
+ for (api in allProviders) {
+ if (url.startsWith(api.mainUrl))
+ return api
}
return null
}
@@ -162,61 +152,12 @@ object APIHolder {
return null
}
- private var trackerCache: HashMap = hashMapOf()
-
- /**
- * Get anime tracker information based on title, year and type.
- * Both titles are attempted to be matched with both Romaji and English title.
- * Uses the consumet api.
- *
- * @param titles uses first index to search, but if you have multiple titles and want extra guarantee to match you can also have that
- * @param types Optional parameter to narrow down the scope to Movies, TV, etc. See TrackerType.getTypes()
- * @param year Optional parameter to only get anime with a specific year
- **/
- suspend fun getTracker(
- titles: List,
- types: Set?,
- year: Int?
- ): Tracker? {
- return try {
- require(titles.isNotEmpty()) { "titles must no be empty when calling getTracker" }
-
- val mainTitle = titles[0]
- val search =
- trackerCache[mainTitle]
- ?: app.get("https://api.consumet.org/meta/anilist/$mainTitle")
- .parsedSafe()?.also {
- trackerCache[mainTitle] = it
- } ?: return null
-
- val res = search.results?.find { media ->
- val matchingYears = year == null || media.releaseDate == year
- val matchingTitles = media.title?.let { title ->
- titles.any { userTitle ->
- title.isMatchingTitles(userTitle)
- }
- } ?: false
-
- val matchingTypes = types?.any { it.name.equals(media.type, true) } == true
- matchingTitles && matchingTypes && matchingYears
- } ?: return null
-
- Tracker(res.malId, res.aniId, res.image, res.cover)
- } catch (t: Throwable) {
- logError(t)
- null
- }
- }
-
-
fun Context.getApiSettings(): HashSet {
//val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
val hashSet = HashSet()
val activeLangs = getApiProviderLangSettings()
- val hasUniversal = activeLangs.contains(AllLanguagesName)
- hashSet.addAll(apis.filter { hasUniversal || activeLangs.contains(it.lang) }
- .map { it.name })
+ hashSet.addAll(apis.filter { activeLangs.contains(it.lang) }.map { it.name })
/*val set = settingsManager.getStringSet(
this.getString(R.string.search_providers_list_key),
@@ -252,11 +193,11 @@ object APIHolder {
fun Context.getApiProviderLangSettings(): HashSet {
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
- val hashSet = hashSetOf(AllLanguagesName) // def is all languages
-// hashSet.add("en") // def is only en
+ val hashSet = HashSet()
+ hashSet.add("en") // def is only en
val list = settingsManager.getStringSet(
this.getString(R.string.provider_lang_key),
- hashSet
+ hashSet.toMutableSet()
)
if (list.isNullOrEmpty()) return hashSet
@@ -286,24 +227,13 @@ object APIHolder {
}
private fun Context.getHasTrailers(): Boolean {
+ if (isTvSettings()) return false
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
return settingsManager.getBoolean(this.getString(R.string.show_trailers_key), true)
}
fun Context.filterProviderByPreferredMedia(hasHomePageIsRequired: Boolean = true): List {
- // We are getting the weirdest crash ever done:
- // java.lang.ClassCastException: com.lagradost.cloudstream3.TvType cannot be cast to com.lagradost.cloudstream3.TvType
- // Trying fixing using classloader fuckery
- val oldLoader = Thread.currentThread().contextClassLoader
- Thread.currentThread().contextClassLoader = TvType::class.java.classLoader
-
- val default = TvType.values()
- .sorted()
- .filter { it != TvType.NSFW }
- .map { it.ordinal }
-
- Thread.currentThread().contextClassLoader = oldLoader
-
+ val default = enumValues().sorted().filter { it != TvType.NSFW }.map { it.ordinal }
val defaultSet = default.map { it.toString() }.toSet()
val currentPrefMedia = try {
PreferenceManager.getDefaultSharedPreferences(this)
@@ -313,8 +243,7 @@ object APIHolder {
null
} ?: default
val langs = this.getApiProviderLangSettings()
- val hasUniversal = langs.contains(AllLanguagesName)
- val allApis = apis.filter { hasUniversal || langs.contains(it.lang) }
+ val allApis = apis.filter { langs.contains(it.lang) }
.filter { api -> api.hasMainPage || !hasHomePageIsRequired }
return if (currentPrefMedia.isEmpty()) {
allApis
@@ -367,57 +296,6 @@ object APIHolder {
}
}
-/*
-// THIS IS WORK IN PROGRESS API
-interface ITag {
- val name: UiText
-}
-
-data class SimpleTag(override val name: UiText, val data: String) : ITag
-
-enum class SelectType {
- SingleSelect,
- MultiSelect,
- MultiSelectAndExclude,
-}
-
-enum class SelectValue {
- Selected,
- Excluded,
-}
-
-interface GenreSelector {
- val title: UiText
- val id : Int
-}
-
-data class TagSelector(
- override val title: UiText,
- override val id : Int,
- val tags: Set,
- val defaultTags : Set = setOf(),
- val selectType: SelectType = SelectType.SingleSelect,
-) : GenreSelector
-
-data class BoolSelector(
- override val title: UiText,
- override val id : Int,
-
- val defaultValue : Boolean = false,
-) : GenreSelector
-
-data class InputField(
- override val title: UiText,
- override val id : Int,
-
- val hint : UiText? = null,
-) : GenreSelector
-
-// This response describes how a user might filter the homepage or search results
-data class GenreResponse(
- val searchSelectors : List,
- val filterSelectors: List = searchSelectors
-) */
/*
0 = Site not good
@@ -446,24 +324,13 @@ data class SettingsJson(
data class MainPageData(
val name: String,
val data: String,
- val horizontalImages: Boolean = false
)
data class MainPageRequest(
val name: String,
val data: String,
- val horizontalImages: Boolean,
- //TODO genre selection or smth
)
-fun mainPage(url: String, name: String, horizontalImages: Boolean = false): MainPageData {
- return MainPageData(name = name, data = url, horizontalImages = horizontalImages)
-}
-
-fun mainPageOf(vararg elements: MainPageData): List {
- return elements.toList()
-}
-
/** return list of MainPageData with url to name, make for more readable code */
fun mainPageOf(vararg elements: Pair): List {
return elements.map { (url, name) -> MainPageData(name = name, data = url) }
@@ -472,7 +339,7 @@ fun mainPageOf(vararg elements: Pair): List {
fun newHomePageResponse(
name: String,
list: List,
- hasNext: Boolean? = null,
+ hasNext: Boolean? = null
): HomePageResponse {
return HomePageResponse(
listOf(HomePageList(name, list)),
@@ -480,17 +347,6 @@ fun newHomePageResponse(
)
}
-fun newHomePageResponse(
- data: MainPageRequest,
- list: List,
- hasNext: Boolean? = null,
-): HomePageResponse {
- return HomePageResponse(
- listOf(HomePageList(data.name, list, data.horizontalImages)),
- hasNext = hasNext ?: list.isNotEmpty()
- )
-}
-
fun newHomePageResponse(list: HomePageList, hasNext: Boolean? = null): HomePageResponse {
return HomePageResponse(listOf(list), hasNext = hasNext ?: list.list.isNotEmpty())
}
@@ -525,19 +381,7 @@ abstract class MainAPI {
open var storedCredentials: String? = null
open var canBeOverridden: Boolean = true
- /** if this is turned on then it will request the homepage one after the other,
- used to delay if they block many request at the same time*/
- open var sequentialMainPage: Boolean = false
-
- /** in milliseconds, this can be used to add more delay between homepage requests
- * on first load if sequentialMainPage is turned on */
- open var sequentialMainPageDelay: Long = 0L
-
- /** in milliseconds, this can be used to add more delay between homepage requests when scrolling */
- open var sequentialMainPageScrollDelay: Long = 0L
-
- /** used to keep track when last homepage request was in unixtime ms */
- var lastHomepageRequest: Long = 0L
+ //open val uniqueId : Int by lazy { this.name.hashCode() } // in case of duplicate providers you can have a shared id
open var lang = "en" // ISO_639_1 check SubtitleHelper
@@ -559,20 +403,6 @@ abstract class MainAPI {
open val hasMainPage = false
open val hasQuickSearch = false
- /**
- * A set of which ids the provider can open with getLoadUrl()
- * If the set contains SyncIdName.Imdb then getLoadUrl() can be started with
- * an Imdb class which inherits from SyncId.
- *
- * getLoadUrl() is then used to get page url based on that ID.
- *
- * Example:
- * "tt6723592" -> getLoadUrl(ImdbSyncId("tt6723592")) -> "mainUrl/imdb/tt6723592" -> load("mainUrl/imdb/tt6723592")
- *
- * This is used to launch pages from personal lists or recommendations using IDs.
- **/
- open val supportedSyncNames = setOf()
-
open val supportedTypes = setOf(
TvType.Movie,
TvType.TvSeries,
@@ -584,8 +414,7 @@ abstract class MainAPI {
open val vpnStatus = VPNStatus.None
open val providerType = ProviderType.DirectProvider
- //emptyList() //
- open val mainPage = listOf(MainPageData("", "", false))
+ open val mainPage = listOf(MainPageData("", ""))
@WorkerThread
open suspend fun getMainPage(
@@ -643,14 +472,6 @@ abstract class MainAPI {
open fun getVideoInterceptor(extractorLink: ExtractorLink): Interceptor? {
return null
}
-
- /**
- * Get the load() url based on a sync ID like IMDb or MAL.
- * Only contains SyncIds based on supportedSyncUrls.
- **/
- open suspend fun getLoadUrl(name: SyncIdName, id: String): String? {
- return null
- }
}
/** Might need a different implementation for desktop*/
@@ -736,19 +557,6 @@ fun fixTitle(str: String): String {
.replaceFirstChar { char -> if (char.isLowerCase()) char.titlecase(Locale.getDefault()) else it }
}
}
-/**
- * Get rhino context in a safe way as it needs to be initialized on the main thread.
- * Make sure you get the scope using: val scope: Scriptable = rhino.initSafeStandardObjects()
- * Use like the following: rhino.evaluateString(scope, js, "JavaScript", 1, null)
- **/
-suspend fun getRhinoContext(): org.mozilla.javascript.Context {
- return Coroutines.mainWork {
- val rhino = org.mozilla.javascript.Context.enter()
- rhino.initSafeStandardObjects()
- rhino.optimizationLevel = -1
- rhino
- }
-}
/** https://www.imdb.com/title/tt2861424/ -> tt2861424 */
fun imdbUrlToId(url: String): String? {
@@ -1212,7 +1020,7 @@ interface LoadResponse {
) {
if (!isTrailersEnabled || trailerUrls == null) return
trailers.addAll(trailerUrls.map { TrailerData(it, referer, addRaw) })
- /*val trailers = trailerUrls.filter { it.isNotBlank() }.amap { trailerUrl ->
+ /*val trailers = trailerUrls.filter { it.isNotBlank() }.apmap { trailerUrl ->
val links = arrayListOf()
val subs = arrayListOf()
if (!loadExtractor(
@@ -1273,43 +1081,18 @@ interface LoadResponse {
fun getDurationFromString(input: String?): Int? {
val cleanInput = input?.trim()?.replace(" ", "") ?: return null
- //Use first as sometimes the text passes on the 2 other Regex, but failed to provide accurate return value
- Regex("(\\d+\\shr)|(\\d+\\shour)|(\\d+\\smin)|(\\d+\\ssec)").findAll(input).let { values ->
- var seconds = 0
- values.forEach {
- val time_text = it.value
- if (time_text.isNotBlank()) {
- val time = time_text.filter { s -> s.isDigit() }.trim().toInt()
- val scale = time_text.filter { s -> !s.isDigit() }.trim()
- //println("Scale: $scale")
- val timeval = when (scale) {
- "hr", "hour" -> time * 60 * 60
- "min" -> time * 60
- "sec" -> time
- else -> 0
- }
- seconds += timeval
- }
- }
- if (seconds > 0) {
- return seconds / 60
- }
- }
Regex("([0-9]*)h.*?([0-9]*)m").find(cleanInput)?.groupValues?.let { values ->
if (values.size == 3) {
val hours = values[1].toIntOrNull()
val minutes = values[2].toIntOrNull()
- if (minutes != null && hours != null) {
- return hours * 60 + minutes
- }
+ return if (minutes != null && hours != null) {
+ hours * 60 + minutes
+ } else null
}
}
Regex("([0-9]*)m").find(cleanInput)?.groupValues?.let { values ->
if (values.size == 2) {
- val return_value = values[1].toIntOrNull()
- if (return_value != null) {
- return return_value
- }
+ return values[1].toIntOrNull()
}
}
return null
@@ -1327,7 +1110,7 @@ fun LoadResponse?.isAnimeBased(): Boolean {
fun TvType?.isEpisodeBased(): Boolean {
if (this == null) return false
- return (this == TvType.TvSeries || this == TvType.Anime || this == TvType.AsianDrama)
+ return (this == TvType.TvSeries || this == TvType.Anime)
}
@@ -1351,7 +1134,6 @@ interface EpisodeResponse {
var showStatus: ShowStatus?
var nextAiring: NextAiring?
var seasonNames: List?
- fun getLatestEpisodes(): Map
}
@JvmName("addSeasonNamesString")
@@ -1420,18 +1202,7 @@ data class AnimeLoadResponse(
override var nextAiring: NextAiring? = null,
override var seasonNames: List? = null,
override var backgroundPosterUrl: String? = null,
-) : LoadResponse, EpisodeResponse {
- override fun getLatestEpisodes(): Map {
- return episodes.map { (status, episodes) ->
- val maxSeason = episodes.maxOfOrNull { it.season ?: Int.MIN_VALUE }
- .takeUnless { it == Int.MIN_VALUE }
- status to episodes
- .filter { it.season == maxSeason }
- .maxOfOrNull { it.episode ?: Int.MIN_VALUE }
- .takeUnless { it == Int.MIN_VALUE }
- }.toMap()
- }
-}
+) : LoadResponse, EpisodeResponse
/**
* If episodes already exist appends the list.
@@ -1629,17 +1400,7 @@ data class TvSeriesLoadResponse(
override var nextAiring: NextAiring? = null,
override var seasonNames: List? = null,
override var backgroundPosterUrl: String? = null,
-) : LoadResponse, EpisodeResponse {
- override fun getLatestEpisodes(): Map {
- val maxSeason =
- episodes.maxOfOrNull { it.season ?: Int.MIN_VALUE }.takeUnless { it == Int.MIN_VALUE }
- val max = episodes
- .filter { it.season == maxSeason }
- .maxOfOrNull { it.episode ?: Int.MIN_VALUE }
- .takeUnless { it == Int.MIN_VALUE }
- return mapOf(DubStatus.None to max)
- }
-}
+) : LoadResponse, EpisodeResponse
suspend fun MainAPI.newTvSeriesLoadResponse(
name: String,
@@ -1671,61 +1432,3 @@ fun fetchUrls(text: String?): List {
fun String?.toRatingInt(): Int? =
this?.replace(" ", "")?.trim()?.toDoubleOrNull()?.absoluteValue?.times(1000f)?.toInt()
-
-data class Tracker(
- val malId: Int? = null,
- val aniId: String? = null,
- val image: String? = null,
- val cover: String? = null,
-)
-
-data class Title(
- @JsonProperty("romaji") val romaji: String? = null,
- @JsonProperty("english") val english: String? = null,
-) {
- fun isMatchingTitles(title: String?): Boolean {
- if (title == null) return false
- return english.equals(title, true) || romaji.equals(title, true)
- }
-}
-
-data class Results(
- @JsonProperty("id") val aniId: String? = null,
- @JsonProperty("malId") val malId: Int? = null,
- @JsonProperty("title") val title: Title? = null,
- @JsonProperty("releaseDate") val releaseDate: Int? = null,
- @JsonProperty("type") val type: String? = null,
- @JsonProperty("image") val image: String? = null,
- @JsonProperty("cover") val cover: String? = null,
-)
-
-data class AniSearch(
- @JsonProperty("results") val results: ArrayList? = arrayListOf()
-)
-
-/**
- * used for the getTracker() method
- **/
-enum class TrackerType {
- MOVIE,
- TV,
- TV_SHORT,
- ONA,
- OVA,
- SPECIAL,
- MUSIC;
-
- companion object {
- fun getTypes(type: TvType): Set {
- return when (type) {
- TvType.Movie -> setOf(MOVIE)
- TvType.AnimeMovie -> setOf(MOVIE)
- TvType.TvSeries -> setOf(TV, TV_SHORT)
- TvType.Anime -> setOf(TV, TV_SHORT, ONA, OVA)
- TvType.OVA -> setOf(OVA, SPECIAL, ONA)
- TvType.Others -> setOf(MUSIC)
- else -> emptySet()
- }
- }
- }
-}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
index d054f504..aa64caa6 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
@@ -1,25 +1,22 @@
package com.lagradost.cloudstream3
import android.content.ComponentName
-import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
import android.content.res.Configuration
-import android.net.Uri
-import android.os.Build
+import android.content.res.Resources
import android.os.Bundle
-import android.util.AttributeSet
import android.util.Log
-import android.view.*
-import android.widget.Toast
+import android.view.KeyEvent
+import android.view.Menu
+import android.view.MenuItem
+import android.view.WindowManager
import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.IdRes
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
-import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible
import androidx.fragment.app.FragmentActivity
-import androidx.lifecycle.ViewModelProvider
import androidx.navigation.NavController
import androidx.navigation.NavDestination
import androidx.navigation.NavDestination.Companion.hierarchy
@@ -32,9 +29,7 @@ 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.*
-import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.navigationrail.NavigationRailView
-import com.google.android.material.snackbar.Snackbar
import com.jaredrummler.android.colorpicker.ColorPickerDialogListener
import com.lagradost.cloudstream3.APIHolder.allProviders
import com.lagradost.cloudstream3.APIHolder.apis
@@ -49,78 +44,58 @@ import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent
import com.lagradost.cloudstream3.CommonActivity.onUserLeaveHint
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.CommonActivity.updateLocale
-import com.lagradost.cloudstream3.mvvm.*
+import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.network.initClient
import com.lagradost.cloudstream3.plugins.PluginManager
-import com.lagradost.cloudstream3.plugins.PluginManager.loadAllOnlinePlugins
import com.lagradost.cloudstream3.plugins.PluginManager.loadSinglePlugin
import com.lagradost.cloudstream3.receivers.VideoDownloadRestartReceiver
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.OAuth2Apis
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.accountManagers
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appString
-import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringPlayer
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringRepo
-import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringResumeWatching
-import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringSearch
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.inAppAuths
import com.lagradost.cloudstream3.ui.APIRepository
-import com.lagradost.cloudstream3.ui.WatchType
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO
-import com.lagradost.cloudstream3.ui.home.HomeViewModel
-import com.lagradost.cloudstream3.ui.player.BasicLink
-import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
-import com.lagradost.cloudstream3.ui.player.LinkGenerator
-import com.lagradost.cloudstream3.ui.result.ResultViewModel2
-import com.lagradost.cloudstream3.ui.result.START_ACTION_RESUME_LATEST
-import com.lagradost.cloudstream3.ui.result.setImage
-import com.lagradost.cloudstream3.ui.result.setText
-import com.lagradost.cloudstream3.ui.search.SearchFragment
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings
-import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
-import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.updateTv
import com.lagradost.cloudstream3.ui.settings.SettingsGeneral
import com.lagradost.cloudstream3.ui.setup.HAS_DONE_SETUP_KEY
import com.lagradost.cloudstream3.ui.setup.SetupFragmentExtensions
-import com.lagradost.cloudstream3.utils.*
-import com.lagradost.cloudstream3.utils.AppUtils.html
+import com.lagradost.cloudstream3.utils.Event
+import com.lagradost.cloudstream3.utils.IOnBackPressed
+import com.lagradost.cloudstream3.utils.resources.ResourcePatch
import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable
-import com.lagradost.cloudstream3.utils.AppUtils.isNetworkAvailable
import com.lagradost.cloudstream3.utils.AppUtils.loadCache
import com.lagradost.cloudstream3.utils.AppUtils.loadRepository
import com.lagradost.cloudstream3.utils.AppUtils.loadResult
-import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
-import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus
import com.lagradost.cloudstream3.utils.BackupUtils.setUpBackup
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
-import com.lagradost.cloudstream3.utils.Coroutines.main
import com.lagradost.cloudstream3.utils.DataStore.getKey
import com.lagradost.cloudstream3.utils.DataStore.setKey
import com.lagradost.cloudstream3.utils.DataStoreHelper.migrateResumeWatching
+import com.lagradost.cloudstream3.utils.DataStoreHelper.setViewPos
import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate
-import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
import com.lagradost.cloudstream3.utils.UIHelper.changeStatusBarState
import com.lagradost.cloudstream3.utils.UIHelper.checkWrite
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
-import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
import com.lagradost.cloudstream3.utils.UIHelper.getResourceColor
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
import com.lagradost.cloudstream3.utils.UIHelper.navigate
import com.lagradost.cloudstream3.utils.UIHelper.requestRW
+import com.lagradost.cloudstream3.utils.USER_PROVIDER_API
+import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API
+import com.lagradost.cloudstream3.utils.resources.ResourcePackManager
import com.lagradost.nicehttp.Requests
import com.lagradost.nicehttp.ResponseParser
import kotlinx.android.synthetic.main.activity_main.*
-import kotlinx.android.synthetic.main.bottom_resultview_preview.*
import kotlinx.android.synthetic.main.fragment_result_swipe.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.io.File
import java.net.URI
-import java.net.URLDecoder
import java.nio.charset.Charset
import kotlin.reflect.KClass
-import kotlin.system.exitProcess
//https://github.com/videolan/vlc-android/blob/3706c4be2da6800b3d26344fc04fab03ffa4b860/application/vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerActivity.kt#L1898
@@ -141,15 +116,13 @@ val VLC_COMPONENT = ComponentName(VLC_PACKAGE, "$VLC_PACKAGE.gui.video.VideoPlay
val MPV_COMPONENT = ComponentName(MPV_PACKAGE, "$MPV_PACKAGE.MPVActivity")
//TODO REFACTOR AF
-open class ResultResume(
+data class ResultResume(
val packageString: String,
val action: String = Intent.ACTION_VIEW,
val position: String? = null,
val duration: String? = null,
var launcher: ActivityResultLauncher? = null,
) {
- val defaultTime = -1L
-
val lastId get() = "${packageString}_last_open_id"
suspend fun launch(id: Int?, callback: suspend Intent.() -> Unit) {
val intent = Intent(action)
@@ -163,50 +136,21 @@ open class ResultResume(
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(
+val VLC = 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
- },
+ "org.videolan.vlc.player.result",
"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(
+val MPV = ResultResume(
MPV_PACKAGE,
//"is.xyz.mpv.MPVActivity.result", // resume not working :pensive:
- position = "position",
+ 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)
@@ -242,33 +186,23 @@ var app = Requests(responseParser = object : ResponseParser {
}
class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
+ private var resourcePatch: ResourcePatch? = null
+
+ override fun getResources(): Resources {
+ return resourcePatch ?: super.getResources()
+ }
+
companion object {
const val TAG = "MAINACT"
- /**
- * Setting this will automatically enter the query in the search
- * next time the search fragment is opened.
- * This variable will clear itself after one use. Null does nothing.
- *
- * This is a very bad solution but I was unable to find a better one.
- **/
- private var nextSearchQuery: String? = null
-
/**
* Fires every time a new batch of plugins have been loaded, no guarantee about how often this is run and on which thread
- * Boolean signifies if stuff should be force reloaded (true if force reload, false if reload when necessary).
- *
- * The force reloading are used for plugin development to instantly reload the page on deployWithAdb
* */
val afterPluginsLoadedEvent = Event()
val mainPluginsLoadedEvent =
Event() // homepage api, used to speed up time to load for homepage
val afterRepositoryLoadedEvent = Event()
- // kinda shitty solution, but cant com main->home otherwise for popups
- val bookmarksUpdatedEvent = Event()
-
-
/**
* @return true if the str has launched an app task (be it successful or not)
* @param isWebview does not handle providers and opening download page if true. Can still add repos and login.
@@ -279,11 +213,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
isWebview: Boolean
): Boolean =
with(activity) {
- // TODO MUCH BETTER HANDLING
-
- // Invalid URIs can crash
- fun safeURI(uri: String) = normalSafeApiCall { URI(uri) }
-
if (str != null && this != null) {
if (str.startsWith("https://cs.repo")) {
val realUrl = "https://" + str.substringAfter("?")
@@ -319,50 +248,10 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
return true
}
}
- // This specific intent is used for the gradle deployWithAdb
- // https://github.com/recloudstream/gradle/blob/master/src/main/kotlin/com/lagradost/cloudstream3/gradle/tasks/DeployWithAdbTask.kt#L46
- if (str == "$appString:") {
- PluginManager.hotReloadAllLocalPlugins(activity)
- }
- } else if (safeURI(str)?.scheme == appStringRepo) {
+ } else if (URI(str).scheme == appStringRepo) {
val url = str.replaceFirst(appStringRepo, "https")
loadRepository(url)
return true
- } else if (safeURI(str)?.scheme == appStringSearch) {
- nextSearchQuery =
- URLDecoder.decode(str.substringAfter("$appStringSearch://"), "UTF-8")
-
- // Use both navigation views to support both layouts.
- // It might be better to use the QuickSearch.
- nav_view?.selectedItemId = R.id.navigation_search
- nav_rail_view?.selectedItemId = R.id.navigation_search
- } else if (safeURI(str)?.scheme == appStringPlayer) {
- val uri = Uri.parse(str)
- val name = uri.getQueryParameter("name")
- val url = URLDecoder.decode(uri.authority, "UTF-8")
-
- navigate(
- R.id.global_to_navigation_player,
- GeneratorPlayer.newInstance(
- LinkGenerator(
- listOf(BasicLink(url, name)),
- extract = true,
- )
- )
- )
- } else if (safeURI(str)?.scheme == appStringResumeWatching) {
- val id =
- str.substringAfter("$appStringResumeWatching://").toIntOrNull()
- ?: return false
- ioSafe {
- val resumeWatchingCard =
- HomeViewModel.getResumeWatching()?.firstOrNull { it.id == id }
- ?: return@ioSafe
- activity.loadSearchResult(
- resumeWatchingCard,
- START_ACTION_RESUME_LATEST
- )
- }
} else if (!isWebview) {
if (str.startsWith(DOWNLOAD_NAVIGATE_TO)) {
this.navigate(R.id.navigation_downloads)
@@ -381,16 +270,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
}
}
- var lastPopup: SearchResponse? = null
- fun loadPopup(result: SearchResponse) {
- lastPopup = result
- viewModel.load(
- this, result.url, result.apiName, false, if (getApiDubstatusSettings()
- .contains(DubStatus.Dubbed)
- ) DubStatus.Dubbed else DubStatus.Subbed, null
- )
- }
-
override fun onColorSelected(dialogId: Int, color: Int) {
onColorSelectedEvent.invoke(Pair(dialogId, color))
}
@@ -422,7 +301,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
val isNavVisible = listOf(
R.id.navigation_home,
R.id.navigation_search,
- R.id.navigation_library,
R.id.navigation_downloads,
R.id.navigation_settings,
R.id.navigation_download_child,
@@ -436,30 +314,8 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
R.id.navigation_settings_general,
R.id.navigation_settings_extensions,
R.id.navigation_settings_plugins,
- R.id.navigation_test_providers,
).contains(destination.id)
-
- val dontPush = listOf(
- R.id.navigation_home,
- R.id.navigation_search,
- R.id.navigation_results_phone,
- R.id.navigation_results_tv,
- R.id.navigation_player,
- ).contains(destination.id)
-
- nav_host_fragment?.apply {
- val params = layoutParams as ConstraintLayout.LayoutParams
-
- params.setMargins(
- if (!dontPush && isTvSettings()) resources.getDimensionPixelSize(R.dimen.navbar_width) else 0,
- params.topMargin,
- params.rightMargin,
- params.bottomMargin
- )
- layoutParams = params
- }
-
val landscape = when (resources.configuration.orientation) {
Configuration.ORIENTATION_LANDSCAPE -> {
true
@@ -474,11 +330,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
nav_view?.isVisible = isNavVisible && !landscape
nav_rail_view?.isVisible = isNavVisible && landscape
-
- // Hide library on TV since it is not supported yet :(
- val isTrueTv = isTrueTvSettings()
- nav_view?.menu?.findItem(R.id.navigation_library)?.isVisible = !isTrueTv
- nav_rail_view?.menu?.findItem(R.id.navigation_library)?.isVisible = !isTrueTv
}
//private var mCastSession: CastSession? = null
@@ -531,11 +382,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
override fun onPause() {
super.onPause()
-
- // Start any delayed updates
- if (ApkInstaller.delayedInstaller?.startInstallation() == true) {
- Toast.makeText(this, R.string.update_started, Toast.LENGTH_LONG).show()
- }
try {
if (isCastApiAvailable()) {
mSessionManager.removeSessionManagerListener(mSessionManagerListener)
@@ -566,34 +412,12 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
onUserLeaveHint(this)
}
- private fun showConfirmExitDialog() {
- val builder: AlertDialog.Builder = AlertDialog.Builder(this)
- builder.setTitle(R.string.confirm_exit_dialog)
- builder.apply {
- // Forceful exit since back button can actually go back to setup
- setPositiveButton(R.string.yes) { _, _ -> exitProcess(0) }
- setNegativeButton(R.string.no) { _, _ -> }
- }
- builder.show().setDefaultFocus()
- }
-
private fun backPressed() {
this.window?.navigationBarColor =
this.colorFromAttribute(R.attr.primaryGrayBackground)
this.updateLocale()
+ super.onBackPressed()
this.updateLocale()
-
- val navHostFragment =
- supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as? NavHostFragment
- val navController = navHostFragment?.navController
- val isAtHome =
- navController?.currentDestination?.matchDestination(R.id.navigation_home) == true
-
- if (isAtHome && isTrueTvSettings()) {
- showConfirmExitDialog()
- } else {
- super.onBackPressed()
- }
}
override fun onBackPressed() {
@@ -681,41 +505,15 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
}
}
- lateinit var viewModel: ResultViewModel2
-
- override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? {
- viewModel =
- ViewModelProvider(this)[ResultViewModel2::class.java]
-
- return super.onCreateView(name, context, attrs)
- }
-
- private fun hidePreviewPopupDialog() {
- viewModel.clear()
- bottomPreviewPopup.dismissSafe(this)
- }
-
- var bottomPreviewPopup: BottomSheetDialog? = null
- private fun showPreviewPopupDialog(): BottomSheetDialog {
- val ret = (bottomPreviewPopup ?: run {
- val builder =
- BottomSheetDialog(this)
- builder.setContentView(R.layout.bottom_resultview_preview)
- builder.setOnDismissListener {
- bottomPreviewPopup = null
- viewModel.clear()
- }
- builder.setCanceledOnTouchOutside(true)
- builder.show()
- builder
- })
- bottomPreviewPopup = ret
- return ret
- }
override fun onCreate(savedInstanceState: Bundle?) {
app.initClient(this)
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
+ resourcePatch = try {
+ ResourcePackManager.activePack?.invoke(super.getResources())
+ } catch (e: Throwable) {
+ null
+ }
val errorFile = filesDir.resolve("last_error")
var lastError: String? = null
@@ -742,7 +540,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
}
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN)
- updateTv()
+
if (isTvSettings()) {
setContentView(R.layout.activity_main_tv)
} else {
@@ -751,35 +549,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
changeStatusBarState(isEmulatorSettings())
- // Automatically enable jsdelivr if cant connect to raw.githubusercontent.com
- if (this.getKey(getString(R.string.jsdelivr_proxy_key)) == null && isNetworkAvailable()) {
- main {
- if (checkGithubConnectivity()) {
- this.setKey(getString(R.string.jsdelivr_proxy_key), false)
- } else {
- this.setKey(getString(R.string.jsdelivr_proxy_key), true)
- val parentView: View = findViewById(android.R.id.content)
- Snackbar.make(parentView, R.string.jsdelivr_enabled, Snackbar.LENGTH_LONG)
- .let { snackbar ->
- snackbar.setAction(R.string.revert) {
- setKey(getString(R.string.jsdelivr_proxy_key), false)
- }
- snackbar.setBackgroundTint(colorFromAttribute(R.attr.primaryGrayBackground))
- snackbar.setTextColor(colorFromAttribute(R.attr.textColor))
- snackbar.setActionTextColor(colorFromAttribute(R.attr.colorPrimary))
- snackbar.show()
- }
- }
-
- }
- }
-
-
- if (PluginManager.checkSafeModeFile()) {
- normalSafeApiCall {
- showToast(this, R.string.safe_mode_file, Toast.LENGTH_LONG)
- }
- } else if (lastError == null) {
+ if (lastError == null) {
ioSafe {
getKey(USER_SELECTED_HOMEPAGE_API)?.let { homeApi ->
mainPluginsLoadedEvent.invoke(loadSinglePlugin(this@MainActivity, homeApi))
@@ -795,21 +565,12 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
) {
PluginManager.updateAllOnlinePluginsAndLoadThem(this@MainActivity)
} else {
- loadAllOnlinePlugins(this@MainActivity)
- }
-
- //Automatically download not existing plugins
- if (settingsManager.getBoolean(
- getString(R.string.auto_download_plugins_key),
- false
- )
- ) {
- PluginManager.downloadNotExistingPluginsAndLoad(this@MainActivity)
+ PluginManager.loadAllOnlinePlugins(this@MainActivity)
}
}
ioSafe {
- PluginManager.loadAllLocalPlugins(this@MainActivity, false)
+ PluginManager.loadAllLocalPlugins(this@MainActivity)
}
}
} else {
@@ -826,81 +587,9 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
setNegativeButton("Ok") { _, _ -> }
}
- builder.show().setDefaultFocus()
+ builder.show()
}
- observeNullable(viewModel.page) { resource ->
- if (resource == null) {
- bottomPreviewPopup.dismissSafe(this)
- return@observeNullable
- }
- when (resource) {
- is Resource.Failure -> {
- showToast(this, R.string.error)
- hidePreviewPopupDialog()
- }
- is Resource.Loading -> {
- showPreviewPopupDialog().apply {
- resultview_preview_loading?.isVisible = true
- resultview_preview_result?.isVisible = false
- resultview_preview_loading_shimmer?.startShimmer()
- }
- }
- is Resource.Success -> {
- val d = resource.value
- showPreviewPopupDialog().apply {
- resultview_preview_loading?.isVisible = false
- resultview_preview_result?.isVisible = true
- resultview_preview_loading_shimmer?.stopShimmer()
-
- resultview_preview_title?.text = d.title
-
- resultview_preview_meta_type.setText(d.typeText)
- resultview_preview_meta_year.setText(d.yearText)
- resultview_preview_meta_duration.setText(d.durationText)
- resultview_preview_meta_rating.setText(d.ratingText)
-
- resultview_preview_description?.setText(d.plotText)
- resultview_preview_poster?.setImage(
- d.posterImage ?: d.posterBackgroundImage
- )
-
- resultview_preview_poster?.setOnClickListener {
- //viewModel.updateWatchStatus(WatchType.PLANTOWATCH)
- val value = viewModel.watchStatus.value ?: WatchType.NONE
-
- this@MainActivity.showBottomDialog(
- WatchType.values().map { getString(it.stringRes) }.toList(),
- value.ordinal,
- this@MainActivity.getString(R.string.action_add_to_bookmarks),
- showApply = false,
- {}) {
- viewModel.updateWatchStatus(WatchType.values()[it])
- bookmarksUpdatedEvent(true)
- }
- }
-
- if (!isTvSettings()) // dont want this clickable on tv layout
- resultview_preview_description?.setOnClickListener { view ->
- view.context?.let { ctx ->
- val builder: AlertDialog.Builder =
- AlertDialog.Builder(ctx, R.style.AlertDialogCustom)
- builder.setMessage(d.plotText.asString(ctx).html())
- .setTitle(d.plotHeaderText.asString(ctx))
- .show()
- }
- }
-
- resultview_preview_more_info?.setOnClickListener {
- hidePreviewPopupDialog()
- lastPopup?.let {
- loadSearchResult(it)
- }
- }
- }
- }
- }
- }
// ioSafe {
// val plugins =
@@ -918,7 +607,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
api.init()
}
- inAppAuths.amap { api ->
+ inAppAuths.apmap { api ->
try {
api.initialize()
} catch (e: Exception) {
@@ -942,17 +631,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController
-
- navController.addOnDestinationChangedListener { _: NavController, navDestination: NavDestination, bundle: Bundle? ->
- // Intercept search and add a query
- if (navDestination.matchDestination(R.id.navigation_search) && !nextSearchQuery.isNullOrBlank()) {
- bundle?.apply {
- this.putString(SearchFragment.SEARCH_QUERY, nextSearchQuery)
- nextSearchQuery = null
- }
- }
- }
-
//val navController = findNavController(R.id.nav_host_fragment)
/*navOptions = NavOptions.Builder()
@@ -966,12 +644,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
nav_view?.setupWithNavController(navController)
val nav_rail = findViewById(R.id.nav_rail_view)
nav_rail?.setupWithNavController(navController)
- if (isTvSettings()) {
- nav_rail?.background?.alpha = 200
- } else {
- nav_rail?.background?.alpha = 255
- }
nav_rail?.setOnItemSelectedListener { item ->
onNavDestinationSelected(
item,
@@ -1140,22 +813,10 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
// Used to check current focus for TV
// main {
// while (true) {
-// delay(5000)
+// delay(1000)
// println("Current focus: $currentFocus")
-// showToast(this, currentFocus.toString(), Toast.LENGTH_LONG)
// }
// }
}
-
- suspend fun checkGithubConnectivity(): Boolean {
- return try {
- app.get(
- "https://raw.githubusercontent.com/recloudstream/.github/master/connectivitycheck",
- timeout = 5
- ).text.trim() == "ok"
- } catch (t: Throwable) {
- false
- }
- }
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ParCollections.kt b/app/src/main/java/com/lagradost/cloudstream3/ParCollections.kt
index 46955427..badb6631 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ParCollections.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ParCollections.kt
@@ -1,7 +1,8 @@
package com.lagradost.cloudstream3
import com.lagradost.cloudstream3.mvvm.logError
-import kotlinx.coroutines.*
+import kotlinx.coroutines.async
+import kotlinx.coroutines.runBlocking
//https://stackoverflow.com/questions/34697828/parallel-operations-on-kotlin-collections
/*
@@ -25,25 +26,10 @@ fun Iterable.pmap(
return ArrayList(destination)
}*/
-
-@OptIn(DelicateCoroutinesApi::class)
-suspend fun Map.amap(f: suspend (Map.Entry) -> R): List =
- with(CoroutineScope(GlobalScope.coroutineContext)) {
- map { async { f(it) } }.map { it.await() }
- }
-
fun Map.apmap(f: suspend (Map.Entry) -> R): List = runBlocking {
map { async { f(it) } }.map { it.await() }
}
-
-@OptIn(DelicateCoroutinesApi::class)
-suspend fun List.amap(f: suspend (A) -> B): List =
- with(CoroutineScope(GlobalScope.coroutineContext)) {
- map { async { f(it) } }.map { it.await() }
- }
-
-
fun List.apmap(f: suspend (A) -> B): List = runBlocking {
map { async { f(it) } }.map { it.await() }
}
@@ -52,12 +38,6 @@ fun List.apmapIndexed(f: suspend (index: Int, A) -> B): List = runB
mapIndexed { index, a -> async { f(index, a) } }.map { it.await() }
}
-@OptIn(DelicateCoroutinesApi::class)
-suspend fun List.amapIndexed(f: suspend (index: Int, A) -> B): List =
- with(CoroutineScope(GlobalScope.coroutineContext)) {
- mapIndexed { index, a -> async { f(index, a) } }.map { it.await() }
- }
-
// run code in parallel
/*fun argpmap(
vararg transforms: () -> R,
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/AStreamHub.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/AStreamHub.kt
index b0051ba7..18602664 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/AStreamHub.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/AStreamHub.kt
@@ -2,11 +2,10 @@ package com.lagradost.cloudstream3.extractors
import android.util.Log
import com.lagradost.cloudstream3.app
-import com.lagradost.cloudstream3.utils.ExtractorApi
-import com.lagradost.cloudstream3.utils.ExtractorLink
-import com.lagradost.cloudstream3.utils.Qualities
+import com.lagradost.cloudstream3.base64Decode
+import com.lagradost.cloudstream3.utils.*
-open class AStreamHub : ExtractorApi() {
+class AStreamHub : ExtractorApi() {
override val name = "AStreamHub"
override val mainUrl = "https://astreamhub.com"
override val requiresReferer = true
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Acefile.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Acefile.kt
index c782b29d..fe46791b 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Acefile.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Acefile.kt
@@ -4,7 +4,7 @@ import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.base64Decode
import com.lagradost.cloudstream3.utils.*
-open class Acefile : ExtractorApi() {
+class Acefile : ExtractorApi() {
override val name = "Acefile"
override val mainUrl = "https://acefile.co"
override val requiresReferer = false
@@ -27,6 +27,7 @@ open class Acefile : ExtractorApi() {
res.substringAfter("\"file\":\"").substringBefore("\","),
"$mainUrl/",
Qualities.Unknown.value,
+ headers = mapOf("range" to "bytes=0-")
)
)
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/AsianLoad.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/AsianLoad.kt
index 7a62fb52..cf16f200 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/AsianLoad.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/AsianLoad.kt
@@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.utils.M3u8Helper
import com.lagradost.cloudstream3.utils.getQualityFromName
import java.net.URI
-open class AsianLoad : ExtractorApi() {
+class AsianLoad : ExtractorApi() {
override var name = "AsianLoad"
override var mainUrl = "https://asianembed.io"
override val requiresReferer = true
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Blogger.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Blogger.kt
index 44e700b1..cae77322 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Blogger.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Blogger.kt
@@ -5,7 +5,7 @@ import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
-open class Blogger : ExtractorApi() {
+class Blogger : ExtractorApi() {
override val name = "Blogger"
override val mainUrl = "https://www.blogger.com"
override val requiresReferer = false
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/BullStream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/BullStream.kt
index 71fa7066..d4f87f4c 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/BullStream.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/BullStream.kt
@@ -5,7 +5,7 @@ import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.M3u8Helper
-open class BullStream : ExtractorApi() {
+class BullStream : ExtractorApi() {
override val name = "BullStream"
override val mainUrl = "https://bullstream.xyz"
override val requiresReferer = false
@@ -18,7 +18,7 @@ open class BullStream : ExtractorApi() {
?: return null
val m3u8 = "$mainUrl/m3u8/${data[1]}/${data[2]}/master.txt?s=1&cache=${data[4]}"
- //println("shiv : $m3u8")
+ println("shiv : $m3u8")
return M3u8Helper.generateM3u8(
name,
m3u8,
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/ByteShare.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/ByteShare.kt
deleted file mode 100644
index 3e0a03c0..00000000
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/ByteShare.kt
+++ /dev/null
@@ -1,23 +0,0 @@
-package com.lagradost.cloudstream3.extractors
-
-import com.lagradost.cloudstream3.utils.*
-
-open class ByteShare : ExtractorApi() {
- override val name = "ByteShare"
- override val mainUrl = "https://byteshare.net"
- override val requiresReferer = false
-
- override suspend fun getUrl(url: String, referer: String?): List {
- val sources = mutableListOf()
- sources.add(
- ExtractorLink(
- name,
- name,
- url.replace("/embed/", "/download/"),
- "",
- Qualities.Unknown.value,
- )
- )
- return sources
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Cda.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Cda.kt
deleted file mode 100644
index 6a2f399d..00000000
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Cda.kt
+++ /dev/null
@@ -1,97 +0,0 @@
-package com.lagradost.cloudstream3.extractors
-
-import com.lagradost.cloudstream3.*
-import com.lagradost.cloudstream3.utils.Qualities
-import com.lagradost.cloudstream3.utils.ExtractorApi
-import com.lagradost.cloudstream3.utils.ExtractorLink
-import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8
-import com.fasterxml.jackson.annotation.JsonProperty
-import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
-import android.util.Log
-import java.net.URLDecoder
-
-open class Cda: ExtractorApi() {
- override var mainUrl = "https://ebd.cda.pl"
- override var name = "Cda"
- override val requiresReferer = false
-
-
- override suspend fun getUrl(url: String, referer: String?): List? {
- val mediaId = url
- .split("/").last()
- .split("?").first()
- val doc = app.get("https://ebd.cda.pl/647x500/$mediaId", headers=mapOf(
- "Referer" to "https://ebd.cda.pl/647x500/$mediaId",
- "User-Agent" to USER_AGENT,
- "Cookie" to "cda.player=html5"
- )).document
- val dataRaw = doc.selectFirst("[player_data]")?.attr("player_data") ?: return null
- val playerData = tryParseJson(dataRaw) ?: return null
- return listOf(ExtractorLink(
- name,
- name,
- getFile(playerData.video.file),
- referer = "https://ebd.cda.pl/647x500/$mediaId",
- quality = Qualities.Unknown.value
- ))
- }
-
- private fun rot13(a: String): String {
- return a.map {
- when {
- it in 'A'..'M' || it in 'a'..'m' -> it + 13
- it in 'N'..'Z' || it in 'n'..'z' -> it - 13
- else -> it
- }
- }.joinToString("")
- }
-
- private fun cdaUggc(a: String): String {
- val decoded = rot13(a)
- return if (decoded.endsWith("adc.mp4")) decoded.replace("adc.mp4",".mp4")
- else decoded
- }
-
- private fun cdaDecrypt(b: String): String {
- var a = b
- .replace("_XDDD", "")
- .replace("_CDA", "")
- .replace("_ADC", "")
- .replace("_CXD", "")
- .replace("_QWE", "")
- .replace("_Q5", "")
- .replace("_IKSDE", "")
- a = URLDecoder.decode(a, "UTF-8")
- a = a.map { char ->
- if (32 < char.toInt() && char.toInt() < 127) {
- return@map String.format("%c", 33 + (char.toInt() + 14) % 94)
- } else {
- return@map char
- }
- }.joinToString("")
- a = a
- .replace(".cda.mp4", "")
- .replace(".2cda.pl", ".cda.pl")
- .replace(".3cda.pl", ".cda.pl")
- return if (a.contains("/upstream")) "https://" + a.replace("/upstream", ".mp4/upstream")
- else "https://${a}.mp4"
- }
-
- private fun getFile(a: String) = when {
- a.startsWith("uggc") -> cdaUggc(a)
- !a.startsWith("http") -> cdaDecrypt(a)
- else -> a
- }
-
- data class VideoPlayerData(
- val file: String,
- val qualities: Map = mapOf(),
- val quality: String?,
- val ts: Int?,
- val hash2: String?
- )
-
- data class PlayerData(
- val video: VideoPlayerData
- )
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Dailymotion.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Dailymotion.kt
deleted file mode 100644
index 4b7cb19f..00000000
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Dailymotion.kt
+++ /dev/null
@@ -1,105 +0,0 @@
-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.utils.AppUtils.tryParseJson
-import com.lagradost.cloudstream3.utils.ExtractorApi
-import com.lagradost.cloudstream3.utils.ExtractorLink
-import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8
-import com.lagradost.cloudstream3.utils.Qualities
-import java.net.URL
-
-open class Dailymotion : ExtractorApi() {
- override val mainUrl = "https://www.dailymotion.com"
- override val name = "Dailymotion"
- override val requiresReferer = false
-
- @Suppress("RegExpSimplifiable")
- private val videoIdRegex = "^[kx][a-zA-Z0-9]+\$".toRegex()
-
- // https://www.dailymotion.com/video/k3JAHfletwk94ayCVIu
- // https://www.dailymotion.com/embed/video/k3JAHfletwk94ayCVIu
- override suspend fun getUrl(
- url: String,
- referer: String?,
- subtitleCallback: (SubtitleFile) -> Unit,
- callback: (ExtractorLink) -> Unit
- ) {
- val embedUrl = getEmbedUrl(url) ?: return
- val doc = app.get(embedUrl).document
- val prefix = "window.__PLAYER_CONFIG__ = "
- val configStr = doc.selectFirst("script:containsData($prefix)")?.data() ?: return
- val config = tryParseJson(configStr.substringAfter(prefix)) ?: return
- val id = getVideoId(embedUrl) ?: return
- val dmV1st = config.dmInternalData.v1st
- val dmTs = config.dmInternalData.ts
- val metaDataUrl =
- "$mainUrl/player/metadata/video/$id?locale=en&dmV1st=$dmV1st&dmTs=$dmTs&is_native_app=0"
- val cookies = mapOf(
- "v1st" to dmV1st,
- "dmvk" to config.context.dmvk,
- "ts" to dmTs.toString()
- )
- val metaData = app.get(metaDataUrl, referer = embedUrl, cookies = cookies)
- .parsedSafe() ?: return
- metaData.qualities.forEach { (_, video) ->
- video.forEach {
- getStream(it.url, this.name, callback)
- }
- }
- }
-
- private fun getEmbedUrl(url: String): String? {
- if (url.contains("/embed/")) {
- return url
- }
- val vid = getVideoId(url) ?: return null
- return "$mainUrl/embed/video/$vid"
- }
-
- private fun getVideoId(url: String): String? {
- val path = URL(url).path
- val id = path.substringAfter("video/")
- if (id.matches(videoIdRegex)) {
- return id
- }
- return null
- }
-
- private suspend fun getStream(
- streamLink: String,
- name: String,
- callback: (ExtractorLink) -> Unit
- ) {
- return generateM3u8(
- name,
- streamLink,
- "",
- ).forEach(callback)
- }
- data class Config(
- val context: Context,
- val dmInternalData: InternalData
- )
-
- data class InternalData(
- val ts: Int,
- val v1st: String
- )
-
- data class Context(
- @JsonProperty("access_token") val accessToken: String?,
- val dmvk: String,
- )
-
- data class MetaData(
- val qualities: Map>
- )
-
- data class VideoLink(
- val type: String,
- val url: String
- )
-
-}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/DoodExtractor.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/DoodExtractor.kt
index 0d94eb08..7ec1fb22 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/DoodExtractor.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/DoodExtractor.kt
@@ -38,9 +38,6 @@ class DoodWsExtractor : DoodLaExtractor() {
override var mainUrl = "https://dood.ws"
}
-class DoodYtExtractor : DoodLaExtractor() {
- override var mainUrl = "https://dood.yt"
-}
open class DoodLaExtractor : ExtractorApi() {
override var name = "DoodStream"
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Embedgram.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Embedgram.kt
index 45a06dcc..35569663 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Embedgram.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Embedgram.kt
@@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.getQualityFromName
import com.lagradost.cloudstream3.utils.httpsify
-open class Embedgram : ExtractorApi() {
+class Embedgram : ExtractorApi() {
override val name = "Embedgram"
override val mainUrl = "https://embedgram.com"
override val requiresReferer = true
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Evolaod.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Evolaod.kt
index 3e38b446..4a9f2f52 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Evolaod.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Evolaod.kt
@@ -16,7 +16,26 @@ open class Evoload : ExtractorApi() {
override suspend fun getUrl(url: String, referer: String?): List {
- val id = url.replace("https://evoload.io/e/", "") // wanted media id
+ val lang = url.substring(0, 2)
+ val flag =
+ if (lang == "vo") {
+ " \uD83C\uDDEC\uD83C\uDDE7"
+ }
+ else if (lang == "vf"){
+ " \uD83C\uDDE8\uD83C\uDDF5"
+ } else {
+ ""
+ }
+
+ val cleaned_url = if (lang == "ht") { // if url doesn't contain a flag and the url starts with http://
+ url
+ } else {
+ url.substring(2, url.length)
+ }
+ //println(lang)
+ //println(cleaned_url)
+
+ val id = cleaned_url.replace("https://evoload.io/e/", "") // wanted media id
val csrv_token = app.get("https://csrv.evosrv.com/captcha?m412548=").text // whatever that is
val captchaPass = app.get("https://cd2.evosrv.com/html/jsx/e.jsx").text.take(300).split("captcha_pass = '")[1].split("\'")[0] //extract the captcha pass from the js response (located in the 300 first chars)
val payload = mapOf("code" to id, "csrv_token" to csrv_token, "pass" to captchaPass)
@@ -25,9 +44,9 @@ open class Evoload : ExtractorApi() {
return listOf(
ExtractorLink(
name,
- name,
+ name + flag,
link,
- url,
+ cleaned_url,
Qualities.Unknown.value,
)
)
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Fastream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Fastream.kt
index e8f8c49a..16b109be 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Fastream.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Fastream.kt
@@ -1,54 +1,39 @@
package com.lagradost.cloudstream3.extractors
-import com.lagradost.cloudstream3.amap
+import com.lagradost.cloudstream3.apmap
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8
-import com.lagradost.cloudstream3.utils.getAndUnpack
-import org.jsoup.nodes.Document
-open class Fastream: ExtractorApi() {
+class Fastream: ExtractorApi() {
override var mainUrl = "https://fastream.to"
override var name = "Fastream"
override val requiresReferer = false
- suspend fun getstream(
- response: Document,
- sources: ArrayList): Boolean{
- response.select("script").amap { script ->
- if (script.data().contains(Regex("eval\\(function\\(p,a,c,k,e,[rd]"))) {
- val unpacked = getAndUnpack(script.data())
- //val m3u8regex = Regex("((https:|http:)\\/\\/.*\\.m3u8)")
- val newm3u8link = unpacked.substringAfter("file:\"").substringBefore("\"")
- //val m3u8link = m3u8regex.find(unpacked)?.value ?: return@forEach
+
+
+ override suspend fun getUrl(url: String, referer: String?): List? {
+ val id = Regex("emb\\.html\\?(.*)\\=(enc|)").find(url)?.destructured?.component1() ?: return emptyList()
+ val sources = mutableListOf()
+ val response = app.post("$mainUrl/dl",
+ data = mapOf(
+ Pair("op","embed"),
+ Pair("file_code",id),
+ Pair("auto","1")
+ )).document
+ response.select("script").apmap { script ->
+ if (script.data().contains("sources")) {
+ val m3u8regex = Regex("((https:|http:)\\/\\/.*\\.m3u8)")
+ val m3u8 = m3u8regex.find(script.data())?.value ?: return@apmap
generateM3u8(
name,
- newm3u8link,
+ m3u8,
mainUrl
).forEach { link ->
sources.add(link)
}
}
}
- return true
- }
-
- override suspend fun getUrl(url: String, referer: String?): List {
- val sources = ArrayList()
- val idregex = Regex("emb.html\\?(.*)=")
- if (url.contains(Regex("(emb.html.*fastream)"))) {
- val id = idregex.find(url)?.destructured?.component1() ?: ""
- val response = app.post("https://fastream.to/dl", allowRedirects = false,
- data = mapOf(
- "op" to "embed",
- "file_code" to id,
- "auto" to "1"
- )
- ).document
- getstream(response, sources)
- }
- val response = app.get(url, referer = url).document
- getstream(response, sources)
return sources
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Filesim.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Filesim.kt
index 84fd0552..5c8af1c5 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Filesim.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Filesim.kt
@@ -1,57 +1,38 @@
package com.lagradost.cloudstream3.extractors
-import com.lagradost.cloudstream3.SubtitleFile
+import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.*
-import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8
+import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
-
-class Ztreamhub : Filesim() {
- override val mainUrl: String = "https://ztreamhub.com" //Here 'cause works
- override val name = "Zstreamhub"
-}
-class FileMoon : Filesim() {
- override val mainUrl = "https://filemoon.to"
- override val name = "FileMoon"
-}
-
-class FileMoonSx : Filesim() {
- override val mainUrl = "https://filemoon.sx"
- override val name = "FileMoonSx"
-}
-
-open class Filesim : ExtractorApi() {
+class Filesim : ExtractorApi() {
override val name = "Filesim"
override val mainUrl = "https://files.im"
override val requiresReferer = false
- override suspend fun getUrl(
- url: String,
- referer: String?,
- subtitleCallback: (SubtitleFile) -> Unit,
- callback: (ExtractorLink) -> Unit
- ) {
- val response = app.get(url, referer = mainUrl).document
- response.select("script[type=text/javascript]").map { script ->
- if (script.data().contains(Regex("eval\\(function\\(p,a,c,k,e,[rd]"))) {
- val unpackedscript = getAndUnpack(script.data())
- val m3u8Regex = Regex("file.\\\"(.*?m3u8.*?)\\\"")
- val m3u8 = m3u8Regex.find(unpackedscript)?.destructured?.component1() ?: ""
- if (m3u8.isNotEmpty()) {
- generateM3u8(
- name,
- m3u8,
- mainUrl
- ).forEach(callback)
+ override suspend fun getUrl(url: String, referer: String?): List {
+ val sources = mutableListOf()
+ with(app.get(url).document) {
+ this.select("script").map { script ->
+ if (script.data().contains("eval(function(p,a,c,k,e,d)")) {
+ val data = getAndUnpack(script.data()).substringAfter("sources:[").substringBefore("]")
+ tryParseJson>("[$data]")?.map {
+ M3u8Helper.generateM3u8(
+ name,
+ it.file,
+ "$mainUrl/",
+ ).forEach { m3uData -> sources.add(m3uData) }
+ }
}
}
}
+ return sources
}
- /* private data class ResponseSource(
+ private data class ResponseSource(
@JsonProperty("file") val file: String,
@JsonProperty("type") val type: String?,
@JsonProperty("label") val label: String?
- ) */
+ )
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/GMPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/GMPlayer.kt
index 52c45096..af02ee8a 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/GMPlayer.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/GMPlayer.kt
@@ -3,9 +3,10 @@ package com.lagradost.cloudstream3.extractors
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
+import com.lagradost.cloudstream3.utils.M3u8Helper
import com.lagradost.cloudstream3.utils.Qualities
-open class GMPlayer : ExtractorApi() {
+class GMPlayer : ExtractorApi() {
override val name = "GM Player"
override val mainUrl = "https://gmplayer.xyz"
override val requiresReferer = true
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Gdriveplayer.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Gdriveplayer.kt
index df9c74a4..dfccc118 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Gdriveplayer.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Gdriveplayer.kt
@@ -3,7 +3,6 @@ package com.lagradost.cloudstream3.extractors
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.utils.*
-import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
import org.jsoup.nodes.Element
import java.security.DigestException
import java.security.MessageDigest
@@ -11,47 +10,43 @@ import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec
-class DatabaseGdrive2 : Gdriveplayer() {
- override var mainUrl = "https://databasegdriveplayer.co"
-}
-
class DatabaseGdrive : Gdriveplayer() {
override var mainUrl = "https://series.databasegdriveplayer.co"
}
-class Gdriveplayerapi : Gdriveplayer() {
+class Gdriveplayerapi: Gdriveplayer() {
override val mainUrl: String = "https://gdriveplayerapi.com"
}
-class Gdriveplayerapp : Gdriveplayer() {
+class Gdriveplayerapp: Gdriveplayer() {
override val mainUrl: String = "https://gdriveplayer.app"
}
-class Gdriveplayerfun : Gdriveplayer() {
+class Gdriveplayerfun: Gdriveplayer() {
override val mainUrl: String = "https://gdriveplayer.fun"
}
-class Gdriveplayerio : Gdriveplayer() {
+class Gdriveplayerio: Gdriveplayer() {
override val mainUrl: String = "https://gdriveplayer.io"
}
-class Gdriveplayerme : Gdriveplayer() {
+class Gdriveplayerme: Gdriveplayer() {
override val mainUrl: String = "https://gdriveplayer.me"
}
-class Gdriveplayerbiz : Gdriveplayer() {
+class Gdriveplayerbiz: Gdriveplayer() {
override val mainUrl: String = "https://gdriveplayer.biz"
}
-class Gdriveplayerorg : Gdriveplayer() {
+class Gdriveplayerorg: Gdriveplayer() {
override val mainUrl: String = "https://gdriveplayer.org"
}
-class Gdriveplayerus : Gdriveplayer() {
+class Gdriveplayerus: Gdriveplayer() {
override val mainUrl: String = "https://gdriveplayer.us"
}
-class Gdriveplayerco : Gdriveplayer() {
+class Gdriveplayerco: Gdriveplayer() {
override val mainUrl: String = "https://gdriveplayer.co"
}
@@ -141,10 +136,6 @@ open class Gdriveplayer : ExtractorApi() {
return find(str)?.groupValues?.getOrNull(1)
}
- private fun String.addMarks(str: String): String {
- return this.replace(Regex("\"?$str\"?"), "\"$str\"")
- }
-
override suspend fun getUrl(
url: String,
referer: String?,
@@ -154,19 +145,18 @@ open class Gdriveplayer : ExtractorApi() {
val document = app.get(url).document
val eval = unpackJs(document)?.replace("\\", "") ?: return
- val data = tryParseJson(Regex("data='(\\S+?)'").first(eval)) ?: return
+ val data = AppUtils.tryParseJson(Regex("data='(\\S+?)'").first(eval)) ?: return
val password = Regex("null,['|\"](\\w+)['|\"]").first(eval)
?.split(Regex("\\D+"))
?.joinToString("") {
Char(it.toInt()).toString()
}.let { Regex("var pass = \"(\\S+?)\"").first(it ?: return)?.toByteArray() }
?: throw ErrorLoadingException("can't find password")
- val decryptedData = cryptoAESHandler(data, password, false)?.let { getAndUnpack(it) }?.replace("\\", "")
+ val decryptedData =
+ cryptoAESHandler(data, password, false)?.let { getAndUnpack(it) }?.replace("\\", "")
+ ?.substringAfter("sources:[")?.substringBefore("],")
- val sourceData = decryptedData?.substringAfter("sources:[")?.substringBefore("],")
- val subData = decryptedData?.substringAfter("tracks:[")?.substringBefore("],")
-
- Regex("\"file\":\"(\\S+?)\".*?res=(\\d+)").findAll(sourceData ?: return).map {
+ Regex("\"file\":\"(\\S+?)\".*?res=(\\d+)").findAll(decryptedData ?: return).map {
it.groupValues[1] to it.groupValues[2]
}.toList().distinctBy { it.second }.map { (link, quality) ->
callback.invoke(
@@ -181,17 +171,6 @@ open class Gdriveplayer : ExtractorApi() {
)
}
- subData?.addMarks("file")?.addMarks("kind")?.addMarks("label").let { dataSub ->
- tryParseJson>("[$dataSub]")?.map { sub ->
- subtitleCallback.invoke(
- SubtitleFile(
- sub.label,
- httpsify(sub.file)
- )
- )
- }
- }
-
}
data class AesData(
@@ -200,10 +179,4 @@ open class Gdriveplayer : ExtractorApi() {
@JsonProperty("s") val s: String
)
- data class Tracks(
- @JsonProperty("file") val file: String,
- @JsonProperty("kind") val kind: String,
- @JsonProperty("label") val label: String
- )
-
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/GuardareStream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/GuardareStream.kt
index 2adc00d5..57435161 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/GuardareStream.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/GuardareStream.kt
@@ -1,88 +1,36 @@
package com.lagradost.cloudstream3.extractors
-
import com.fasterxml.jackson.annotation.JsonProperty
-import com.lagradost.cloudstream3.SubtitleFile
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.*
-class Vanfem : GuardareStream() {
- override var name = "Vanfem"
- override var mainUrl = "https://vanfem.com/"
-}
-
-class CineGrabber : GuardareStream() {
- override var name = "CineGrabber"
- override var mainUrl = "https://cinegrabber.com"
-}
-
open class GuardareStream : ExtractorApi() {
override var name = "Guardare"
override var mainUrl = "https://guardare.stream"
override val requiresReferer = false
- data class GuardareJsonData(
- @JsonProperty("data") val data: List,
- @JsonProperty("captions") val captions: List?,
+ data class GuardareJsonData (
+ @JsonProperty("data") val data : List,
)
- data class GuardareData(
- @JsonProperty("file") val file: String,
- @JsonProperty("label") val label: String,
- @JsonProperty("type") val type: String
+ data class GuardareData (
+ @JsonProperty("file") val file : String,
+ @JsonProperty("label") val label : String,
+ @JsonProperty("type") val type : String
)
-
- // https://cinegrabber.com/asset/userdata/224879/caption/gqdmzh-71ez76z8/876438.srt
- data class GuardareCaptions(
- @JsonProperty("id") val id: String,
- @JsonProperty("hash") val hash: String,
- @JsonProperty("language") val language: String?,
- @JsonProperty("extension") val extension: String
- ) {
- fun getUrl(mainUrl: String, userId: String): String {
- return "$mainUrl/asset/userdata/$userId/caption/$hash/$id.$extension"
- }
- }
-
- override suspend fun getUrl(
- url: String,
- referer: String?,
- subtitleCallback: (SubtitleFile) -> Unit,
- callback: (ExtractorLink) -> Unit
- ) {
- val response =
- app.post(url.replace("/v/", "/api/source/"), data = mapOf("d" to mainUrl)).text
-
- val jsonVideoData = AppUtils.parseJson(response)
- jsonVideoData.data.forEach {
- callback.invoke(
- ExtractorLink(
- it.file + ".${it.type}",
- this.name,
- it.file + ".${it.type}",
- mainUrl,
- it.label.filter { it.isDigit() }.toInt(),
- false
- )
+ override suspend fun getUrl(url: String, referer: String?): List? {
+ val response = app.post(url.replace("/v/","/api/source/"), data = mapOf("d" to mainUrl)).text
+ val jsonvideodata = AppUtils.parseJson(response)
+ return jsonvideodata.data.map {
+ ExtractorLink(
+ it.file+".${it.type}",
+ this.name,
+ it.file+".${it.type}",
+ mainUrl,
+ it.label.filter{ it.isDigit() }.toInt(),
+ false
)
}
-
- if (!jsonVideoData.captions.isNullOrEmpty()){
- val iframe = app.get(url)
- // var USER_ID = '224879';
- val userIdRegex = Regex("""USER_ID.*?(\d+)""")
- val userId = userIdRegex.find(iframe.text)?.groupValues?.getOrNull(1) ?: return
- jsonVideoData.captions.forEach {
- if (it == null) return@forEach
- val subUrl = it.getUrl(mainUrl, userId)
- subtitleCallback.invoke(
- SubtitleFile(
- it.language ?: "",
- subUrl
- )
- )
- }
- }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Jeniusplay.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Jeniusplay.kt
deleted file mode 100644
index 11b66d99..00000000
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Jeniusplay.kt
+++ /dev/null
@@ -1,72 +0,0 @@
-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.utils.*
-import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
-
-open class Jeniusplay : ExtractorApi() {
- override val name = "Jeniusplay"
- override val mainUrl = "https://jeniusplay.com"
- override val requiresReferer = true
-
- override suspend fun getUrl(
- url: String,
- referer: String?,
- subtitleCallback: (SubtitleFile) -> Unit,
- callback: (ExtractorLink) -> Unit
- ) {
- val document = app.get(url, referer = "$mainUrl/").document
- val hash = url.split("/").last().substringAfter("data=")
-
- val m3uLink = app.post(
- url = "$mainUrl/player/index.php?data=$hash&do=getVideo",
- data = mapOf("hash" to hash, "r" to "$referer"),
- referer = url,
- headers = mapOf("X-Requested-With" to "XMLHttpRequest")
- ).parsed().videoSource
-
- M3u8Helper.generateM3u8(
- this.name,
- m3uLink,
- url,
- ).forEach(callback)
-
-
- document.select("script").map { script ->
- if (script.data().contains("eval(function(p,a,c,k,e,d)")) {
- val subData =
- getAndUnpack(script.data()).substringAfter("\"tracks\":[").substringBefore("],")
- tryParseJson>("[$subData]")?.map { subtitle ->
- subtitleCallback.invoke(
- SubtitleFile(
- getLanguage(subtitle.label ?: ""),
- subtitle.file
- )
- )
- }
- }
- }
- }
-
- private fun getLanguage(str: String): String {
- return when {
- str.contains("indonesia", true) || str
- .contains("bahasa", true) -> "Indonesian"
- else -> str
- }
- }
-
- data class ResponseSource(
- @JsonProperty("hls") val hls: Boolean,
- @JsonProperty("videoSource") val videoSource: String,
- @JsonProperty("securedLink") val securedLink: String?,
- )
-
- data class Tracks(
- @JsonProperty("kind") val kind: String?,
- @JsonProperty("file") val file: String,
- @JsonProperty("label") val label: String?,
- )
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Linkbox.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Linkbox.kt
index 6a4945bb..52fc5532 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Linkbox.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Linkbox.kt
@@ -1,53 +1,46 @@
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.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.getQualityFromName
-open class Linkbox : ExtractorApi() {
+class Linkbox : ExtractorApi() {
override val name = "Linkbox"
override val mainUrl = "https://www.linkbox.to"
override val requiresReferer = true
- override suspend fun getUrl(
- url: String,
- referer: String?,
- subtitleCallback: (SubtitleFile) -> Unit,
- callback: (ExtractorLink) -> Unit
- ) {
- val id = Regex("""(?:/f/|/file/|\?id=)(\w+)""").find(url)?.groupValues?.get(1)
- app.get("$mainUrl/api/file/detail?itemId=$id", referer = url)
- .parsedSafe()?.data?.itemInfo?.resolutionList?.map { link ->
- callback.invoke(
- ExtractorLink(
- name,
- name,
- link.url ?: return@map null,
- url,
- getQualityFromName(link.resolution)
- )
+ override suspend fun getUrl(url: String, referer: String?): List {
+ val id = url.substringAfter("id=")
+ val sources = mutableListOf()
+
+ app.get("$mainUrl/api/open/get_url?itemId=$id", referer=url).parsedSafe()?.data?.rList?.map { link ->
+ sources.add(
+ ExtractorLink(
+ name,
+ name,
+ link.url,
+ url,
+ getQualityFromName(link.resolution)
)
- }
+ )
+ }
+
+ return sources
}
- data class Resolutions(
- @JsonProperty("url") val url: String? = null,
- @JsonProperty("resolution") val resolution: String? = null,
- )
-
- data class ItemInfo(
- @JsonProperty("resolutionList") val resolutionList: ArrayList? = arrayListOf(),
+ data class RList(
+ @JsonProperty("url") val url: String,
+ @JsonProperty("resolution") val resolution: String?,
)
data class Data(
- @JsonProperty("itemInfo") val itemInfo: ItemInfo? = null,
+ @JsonProperty("rList") val rList: List?,
)
data class Responses(
- @JsonProperty("data") val data: Data? = null,
+ @JsonProperty("data") val data: Data?,
)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Mcloud.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Mcloud.kt
new file mode 100644
index 00000000..29d98557
--- /dev/null
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Mcloud.kt
@@ -0,0 +1,7 @@
+package com.lagradost.cloudstream3.extractors
+
+open class Mcloud : WcoStream() {
+ override var name = "Mcloud"
+ override var mainUrl = "https://mcloud.to"
+ override val requiresReferer = true
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Moviehab.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Moviehab.kt
deleted file mode 100644
index aaa33ca1..00000000
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Moviehab.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-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 MoviehabNet : Moviehab() {
- override var mainUrl = "https://play.moviehab.net"
-}
-
-open class Moviehab : ExtractorApi() {
- override var name = "Moviehab"
- override var mainUrl = "https://play.moviehab.com"
- override val requiresReferer = false
-
- override suspend fun getUrl(
- url: String,
- referer: String?,
- subtitleCallback: (SubtitleFile) -> Unit,
- callback: (ExtractorLink) -> Unit
- ) {
- val res = app.get(url)
- res.document.select("video#player").let {
- //should redirect first for making it works
- val link = app.get("$mainUrl/${it.select("source").attr("src")}", referer = url).url
- M3u8Helper.generateM3u8(
- this.name,
- link,
- url
- ).forEach(callback)
-
- Regex("src[\"|'],\\s[\"|'](\\S+)[\"|']\\)").find(res.text)?.groupValues?.get(1).let {sub ->
- subtitleCallback.invoke(
- SubtitleFile(
- it.select("track").attr("label"),
- "$mainUrl/$sub"
- )
- )
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Mp4Upload.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Mp4Upload.kt
index 93a280ed..68a4a103 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Mp4Upload.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Mp4Upload.kt
@@ -6,7 +6,7 @@ import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.Qualities
import com.lagradost.cloudstream3.utils.getAndUnpack
-open class Mp4Upload : ExtractorApi() {
+class Mp4Upload : ExtractorApi() {
override var name = "Mp4Upload"
override var mainUrl = "https://www.mp4upload.com"
private val srcRegex = Regex("""player\.src\("(.*?)"""")
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/MultiQuality.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/MultiQuality.kt
index 44657196..0c0b5c68 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/MultiQuality.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/MultiQuality.kt
@@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.utils.Qualities
import com.lagradost.cloudstream3.utils.getQualityFromName
import java.net.URI
-open class MultiQuality : ExtractorApi() {
+class MultiQuality : ExtractorApi() {
override var name = "MultiQuality"
override var mainUrl = "https://gogo-play.net"
private val sourceRegex = Regex("""file:\s*['"](.*?)['"],label:\s*['"](.*?)['"]""")
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Mvidoo.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Mvidoo.kt
index 9e5f5e74..c00df942 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Mvidoo.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Mvidoo.kt
@@ -6,7 +6,7 @@ import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.Qualities
-open class Mvidoo : ExtractorApi() {
+class Mvidoo : ExtractorApi() {
override val name = "Mvidoo"
override val mainUrl = "https://mvidoo.com"
override val requiresReferer = true
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Okrulink.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Okrulink.kt
deleted file mode 100644
index 37bb09e3..00000000
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Okrulink.kt
+++ /dev/null
@@ -1,39 +0,0 @@
-package com.lagradost.cloudstream3.extractors
-
-import com.fasterxml.jackson.annotation.JsonProperty
-import com.lagradost.cloudstream3.app
-import com.lagradost.cloudstream3.utils.ExtractorApi
-import com.lagradost.cloudstream3.utils.ExtractorLink
-import com.lagradost.cloudstream3.utils.Qualities
-
-data class Okrulinkdata (
- @JsonProperty("status" ) var status : String? = null,
- @JsonProperty("url" ) var url : String? = null
-)
-
-open class Okrulink: ExtractorApi() {
- override var mainUrl = "https://okru.link"
- override var name = "Okrulink"
- override val requiresReferer = false
-
- override suspend fun getUrl(url: String, referer: String?): List {
- val sources = mutableListOf()
- val key = url.substringAfter("html?t=")
- val request = app.post("https://apizz.okru.link/decoding", allowRedirects = false,
- data = mapOf("video" to key)
- ).parsedSafe()
- if (request?.url != null) {
- sources.add(
- ExtractorLink(
- name,
- name,
- request.url!!,
- "",
- Qualities.Unknown.value,
- isM3u8 = false
- )
- )
- }
- return sources
- }
-}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Pelisplus.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Pelisplus.kt
index 45ec4c2f..cc743d5e 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Pelisplus.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Pelisplus.kt
@@ -1,7 +1,7 @@
package com.lagradost.cloudstream3.extractors
import com.lagradost.cloudstream3.SubtitleFile
-import com.lagradost.cloudstream3.amap
+import com.lagradost.cloudstream3.apmap
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
import com.lagradost.cloudstream3.utils.ExtractorLink
@@ -14,7 +14,7 @@ import org.jsoup.Jsoup
* overrideMainUrl is necessary for for other vidstream clones like vidembed.cc
* If they diverge it'd be better to make them separate.
* */
-open class Pelisplus(val mainUrl: String) {
+class Pelisplus(val mainUrl: String) {
val name: String = "Vidstream"
private fun getExtractorUrl(id: String): String {
@@ -35,7 +35,7 @@ open class Pelisplus(val mainUrl: String) {
callback: (ExtractorLink) -> Unit
): Boolean {
try {
- normalApis.amap { api ->
+ normalApis.apmap { api ->
val url = api.getExtractorUrl(id)
api.getSafeUrl(url, subtitleCallback = subtitleCallback, callback = callback)
}
@@ -51,8 +51,8 @@ open class Pelisplus(val mainUrl: String) {
val qualityRegex = Regex("(\\d+)P")
//a[download]
- pageDoc.select(".dowload > a")?.amap { element ->
- val href = element.attr("href") ?: return@amap
+ pageDoc.select(".dowload > a")?.apmap { element ->
+ val href = element.attr("href") ?: return@apmap
val qual = if (element.text()
.contains("HDP")
) "1080" else qualityRegex.find(element.text())?.destructured?.component1()
@@ -84,7 +84,7 @@ open class Pelisplus(val mainUrl: String) {
//val name = element.text()
// Matches vidstream links with extractors
- extractorApis.filter { !it.requiresReferer || !isCasting }.amap { api ->
+ extractorApis.filter { !it.requiresReferer || !isCasting }.apmap { api ->
if (link.startsWith(api.mainUrl)) {
api.getSafeUrl(link, extractorUrl, subtitleCallback, callback)
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/PlayLtXyz.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/PlayLtXyz.kt
index 2b286abb..9a031556 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/PlayLtXyz.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/PlayLtXyz.kt
@@ -6,7 +6,7 @@ import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
-open class PlayLtXyz: ExtractorApi() {
+class PlayLtXyz: ExtractorApi() {
override val name: String = "PlayLt"
override val mainUrl: String = "https://play.playlt.xyz"
override val requiresReferer = true
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Sendvid.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Sendvid.kt
deleted file mode 100644
index 514b802d..00000000
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Sendvid.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.lagradost.cloudstream3.extractors
-
-import com.lagradost.cloudstream3.SubtitleFile
-import com.lagradost.cloudstream3.utils.*
-import com.lagradost.cloudstream3.app
-import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8
-
-open class Sendvid : ExtractorApi() {
- override var name = "Sendvid"
- override val mainUrl = "https://sendvid.com"
- override val requiresReferer = false
- override suspend fun getUrl(
- url: String,
- referer: String?,
- subtitleCallback: (SubtitleFile) -> Unit,
- callback: (ExtractorLink) -> Unit
- ) {
- val doc = app.get(url).document
- val urlString = doc.select("head meta[property=og:video:secure_url]").attr("content")
- if (urlString.contains("m3u8")) {
- generateM3u8(
- name,
- urlString,
- mainUrl,
- ).forEach(callback)
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Solidfiles.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Solidfiles.kt
index cc34781c..849f5fc8 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Solidfiles.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Solidfiles.kt
@@ -8,7 +8,7 @@ import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.getQualityFromName
-open class Solidfiles : ExtractorApi() {
+class Solidfiles : ExtractorApi() {
override val name = "Solidfiles"
override val mainUrl = "https://www.solidfiles.com"
override val requiresReferer = false
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/SpeedoStream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/SpeedoStream.kt
index 8ef6c463..6153a7c1 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/SpeedoStream.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/SpeedoStream.kt
@@ -7,11 +7,7 @@ import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.M3u8Helper
-class SpeedoStream1 : SpeedoStream() {
- override val mainUrl = "https://speedostream.nl"
-}
-
-open class SpeedoStream : ExtractorApi() {
+class SpeedoStream : ExtractorApi() {
override val name = "SpeedoStream"
override val mainUrl = "https://speedostream.com"
override val requiresReferer = true
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamSB.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamSB.kt
index cac31328..30a0496d 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamSB.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamSB.kt
@@ -7,11 +7,6 @@ import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.M3u8Helper
-class Sbspeed : StreamSB() {
- override var name = "Sbspeed"
- override var mainUrl = "https://sbspeed.com"
-}
-
class Streamsss : StreamSB() {
override var mainUrl = "https://streamsss.net"
}
@@ -77,10 +72,6 @@ class StreamSB10 : StreamSB() {
override var mainUrl = "https://sbplay2.xyz"
}
-class StreamSB11 : StreamSB() {
- override var mainUrl = "https://sbbrisk.com"
-}
-
// This is a modified version of https://github.com/jmir1/aniyomi-extensions/blob/master/src/en/genoanime/src/eu/kanade/tachiyomi/animeextension/en/genoanime/extractors/StreamSBExtractor.kt
// The following code is under the Apache License 2.0 https://github.com/jmir1/aniyomi-extensions/blob/master/LICENSE
open class StreamSB : ExtractorApi() {
@@ -102,15 +93,15 @@ open class StreamSB : ExtractorApi() {
}
data class Subs (
- @JsonProperty("file") val file: String? = null,
- @JsonProperty("label") val label: String? = null,
+ @JsonProperty("file") val file: String,
+ @JsonProperty("label") val label: String,
)
data class StreamData (
@JsonProperty("file") val file: String,
@JsonProperty("cdn_img") val cdnImg: String,
@JsonProperty("hash") val hash: String,
- @JsonProperty("subs") val subs: ArrayList? = arrayListOf(),
+ @JsonProperty("subs") val subs: List?,
@JsonProperty("length") val length: String,
@JsonProperty("id") val id: String,
@JsonProperty("title") val title: String,
@@ -134,7 +125,7 @@ open class StreamSB : ExtractorApi() {
it.value.replace(Regex("(embed-|/e/)"), "")
}.first()
// val master = "$mainUrl/sources48/6d6144797752744a454267617c7c${bytesToHex.lowercase()}7c7c4e61755a56456f34385243727c7c73747265616d7362/6b4a33767968506e4e71374f7c7c343837323439333133333462353935333633373836643638376337633462333634663539343137373761333635313533333835333763376333393636363133393635366136323733343435323332376137633763373337343732363536313664373336327c7c504d754478413835306633797c7c73747265616d7362"
- val master = "$mainUrl/sources15/" + bytesToHex("||$id||||streamsb".toByteArray()) + "/"
+ val master = "$mainUrl/sources48/" + bytesToHex("||$id||||streamsb".toByteArray()) + "/"
val headers = mapOf(
"watchsb" to "sbstream",
)
@@ -150,14 +141,5 @@ open class StreamSB : ExtractorApi() {
url,
headers = headers
).forEach(callback)
-
- mapped.streamData.subs?.map {sub ->
- subtitleCallback.invoke(
- SubtitleFile(
- sub.label.toString(),
- sub.file ?: return@map null,
- )
- )
- }
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamTape.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamTape.kt
index ece8dc4b..af436ff3 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamTape.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamTape.kt
@@ -5,15 +5,7 @@ import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.Qualities
-class StreamTapeNet : StreamTape() {
- override var mainUrl = "https://streamtape.net"
-}
-
-class ShaveTape : StreamTape(){
- override var mainUrl = "https://shavetape.cash"
-}
-
-open class StreamTape : ExtractorApi() {
+class StreamTape : ExtractorApi() {
override var name = "StreamTape"
override var mainUrl = "https://streamtape.com"
override val requiresReferer = false
@@ -24,8 +16,7 @@ open class StreamTape : ExtractorApi() {
override suspend fun getUrl(url: String, referer: String?): List? {
with(app.get(url)) {
linkRegex.find(this.text)?.let {
- val extractedUrl =
- "https:${it.groups[1]!!.value + it.groups[2]!!.value.substring(3)}"
+ val extractedUrl = "https:${it.groups[1]!!.value + it.groups[2]!!.value.substring(3,)}"
return listOf(
ExtractorLink(
name,
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Streamhub.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Streamhub.kt
index c7689c58..2765ae17 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Streamhub.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Streamhub.kt
@@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.utils.JsUnpacker
import com.lagradost.cloudstream3.utils.Qualities
import java.net.URI
-open class Streamhub : ExtractorApi() {
+class Streamhub : ExtractorApi() {
override var mainUrl = "https://streamhub.to"
override var name = "Streamhub"
override val requiresReferer = false
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Streamplay.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Streamplay.kt
index e6bbfeba..3f5e5bd6 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Streamplay.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Streamplay.kt
@@ -9,7 +9,7 @@ import com.lagradost.cloudstream3.utils.*
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
import java.net.URI
-open class Streamplay : ExtractorApi() {
+class Streamplay : ExtractorApi() {
override val name = "Streamplay"
override val mainUrl = "https://streamplay.to"
override val requiresReferer = true
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Supervideo.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Supervideo.kt
index dd49d994..955345aa 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Supervideo.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Supervideo.kt
@@ -11,7 +11,7 @@ data class Files(
@JsonProperty("label") val label: String? = null,
)
-open class Supervideo : ExtractorApi() {
+ open class Supervideo : ExtractorApi() {
override var name = "Supervideo"
override var mainUrl = "https://supervideo.tv"
override val requiresReferer = false
@@ -20,13 +20,10 @@ open class Supervideo : ExtractorApi() {
val response = app.get(url).text
val jstounpack = Regex("eval((.|\\n)*?)").find(response)?.groups?.get(1)?.value
val unpacjed = JsUnpacker(jstounpack).unpack()
- val extractedUrl =
- unpacjed?.let { Regex("""sources:((.|\n)*?)image""").find(it) }?.groups?.get(1)?.value.toString()
- .replace("file", """"file"""").replace("label", """"label"""")
- .substringBeforeLast(",")
+ val extractedUrl = unpacjed?.let { Regex("""sources:((.|\n)*?)image""").find(it) }?.groups?.get(1)?.value.toString().replace("file",""""file"""").replace("label",""""label"""").substringBeforeLast(",")
val parsedlinks = parseJson>(extractedUrl)
parsedlinks.forEach { data ->
- if (data.label.isNullOrBlank()) { // mp4 links (with labels) are slow. Use only m3u8 link.
+ if (data.label.isNullOrBlank()){ // mp4 links (with labels) are slow. Use only m3u8 link.
M3u8Helper.generateM3u8(
name,
data.id,
@@ -37,6 +34,8 @@ open class Supervideo : ExtractorApi() {
}
}
}
+
+
return extractedLinksList
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Tomatomatela.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Tomatomatela.kt
index 28a2eb20..20bd69ba 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Tomatomatela.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Tomatomatela.kt
@@ -1,64 +1,41 @@
package com.lagradost.cloudstream3.extractors
-import com.lagradost.cloudstream3.utils.*
-import com.lagradost.cloudstream3.app
import com.fasterxml.jackson.annotation.JsonProperty
-import com.fasterxml.jackson.module.kotlin.readValue
-import com.lagradost.cloudstream3.USER_AGENT
-import com.lagradost.cloudstream3.mapper
+import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
-
+import com.lagradost.cloudstream3.utils.ExtractorApi
+import com.lagradost.cloudstream3.utils.ExtractorLink
+import com.lagradost.cloudstream3.utils.Qualities
class Cinestart: Tomatomatela() {
- override var name: String = "Cinestart"
- override val mainUrl: String = "https://cinestart.net"
+ override var name = "Cinestart"
+ override var mainUrl = "https://cinestart.net"
override val details = "vr.php?v="
}
-class TomatomatelalClub: Tomatomatela() {
- override var name: String = "Tomatomatela"
- override val mainUrl: String = "https://tomatomatela.club"
-}
-
open class Tomatomatela : ExtractorApi() {
override var name = "Tomatomatela"
- override val mainUrl = "https://tomatomatela.com"
+ override var mainUrl = "https://tomatomatela.com"
override val requiresReferer = false
private data class Tomato (
@JsonProperty("status") val status: Int,
- @JsonProperty("file") val file: String?
+ @JsonProperty("file") val file: String
)
open val details = "details.php?v="
- open val embeddetails = "/embed.html#"
override suspend fun getUrl(url: String, referer: String?): List? {
- val link = url.replace("$mainUrl$embeddetails","$mainUrl/$details")
- val sources = ArrayList()
- val server = app.get(link, allowRedirects = false,
- headers = mapOf(
- "User-Agent" to USER_AGENT,
- "Accept" to "application/json, text/javascript, */*; q=0.01",
- "Accept-Language" to "en-US,en;q=0.5",
- "X-Requested-With" to "XMLHttpRequest",
- "DNT" to "1",
- "Connection" to "keep-alive",
- "Sec-Fetch-Dest" to "empty",
- "Sec-Fetch-Mode" to "cors",
- "Sec-Fetch-Site" to "same-origin"
-
+ val link = url.replace("$mainUrl/embed.html#","$mainUrl/$details")
+ val server = app.get(link, allowRedirects = false).text
+ val json = parseJson(server)
+ if (json.status == 200) return listOf(
+ ExtractorLink(
+ name,
+ name,
+ json.file,
+ "",
+ Qualities.Unknown.value,
+ isM3u8 = false
)
- ).parsedSafe()
- if (server?.file != null) {
- sources.add(
- ExtractorLink(
- name,
- name,
- server.file,
- "",
- Qualities.Unknown.value,
- isM3u8 = false
- )
- )
- }
- return sources
+ )
+ return null
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/UpstreamExtractor.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/UpstreamExtractor.kt
index 09e47d03..1eb384c4 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/UpstreamExtractor.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/UpstreamExtractor.kt
@@ -6,7 +6,7 @@ import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.M3u8Helper
-open class UpstreamExtractor : ExtractorApi() {
+class UpstreamExtractor : ExtractorApi() {
override val name: String = "Upstream"
override val mainUrl: String = "https://upstream.to"
override val requiresReferer = true
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Uqload.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Uqload.kt
index 86bd9e0b..e5d2875f 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Uqload.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Uqload.kt
@@ -7,10 +7,6 @@ class Uqload1 : Uqload() {
override var mainUrl = "https://uqload.com"
}
-class Uqload2 : Uqload() {
- override var mainUrl = "https://uqload.co"
-}
-
open class Uqload : ExtractorApi() {
override val name: String = "Uqload"
override val mainUrl: String = "https://www.uqload.com"
@@ -19,14 +15,30 @@ open class Uqload : ExtractorApi() {
override suspend fun getUrl(url: String, referer: String?): List? {
- with(app.get(url)) { // raised error ERROR_CODE_PARSING_CONTAINER_UNSUPPORTED (3003) is due to the response: "error_nofile"
+ val lang = url.substring(0, 2)
+ val flag =
+ if (lang == "vo") {
+ " \uD83C\uDDEC\uD83C\uDDE7"
+ }
+ else if (lang == "vf"){
+ " \uD83C\uDDE8\uD83C\uDDF5"
+ } else {
+ ""
+ }
+
+ val cleaned_url = if (lang == "ht") { // if url doesn't contain a flag and the url starts with http://
+ url
+ } else {
+ url.substring(2, url.length)
+ }
+ with(app.get(cleaned_url)) { // raised error ERROR_CODE_PARSING_CONTAINER_UNSUPPORTED (3003) is due to the response: "error_nofile"
srcRegex.find(this.text)?.groupValues?.get(1)?.replace("\"", "")?.let { link ->
return listOf(
ExtractorLink(
name,
- name,
+ name + flag,
link,
- url,
+ cleaned_url,
Qualities.Unknown.value,
)
)
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/VidSrcExtractor.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/VidSrcExtractor.kt
index a27bf188..63634704 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/VidSrcExtractor.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/VidSrcExtractor.kt
@@ -1,7 +1,7 @@
package com.lagradost.cloudstream3.extractors
import com.lagradost.cloudstream3.SubtitleFile
-import com.lagradost.cloudstream3.amap
+import com.lagradost.cloudstream3.apmap
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.*
import kotlinx.coroutines.delay
@@ -59,8 +59,8 @@ open class VidSrcExtractor : ExtractorApi() {
if (datahash.isNotBlank()) {
val links = try {
app.get(
- "$absoluteUrl/srcrcp/$datahash",
- referer = "https://rcp.vidsrc.me/"
+ "$absoluteUrl/src/$datahash",
+ referer = "https://source.vidsrc.me/"
).url
} catch (e: Exception) {
""
@@ -69,12 +69,12 @@ open class VidSrcExtractor : ExtractorApi() {
} else ""
}
- serverslist.amap { server ->
+ serverslist.apmap { server ->
val linkfixed = server.replace("https://vidsrc.xyz/", "https://embedsito.com/")
- if (linkfixed.contains("/prorcp")) {
+ if (linkfixed.contains("/pro")) {
val srcresponse = app.get(server, referer = absoluteUrl).text
val m3u8Regex = Regex("((https:|http:)//.*\\.m3u8)")
- val srcm3u8 = m3u8Regex.find(srcresponse)?.value ?: return@amap
+ val srcm3u8 = m3u8Regex.find(srcresponse)?.value ?: return@apmap
val passRegex = Regex("""['"](.*set_pass[^"']*)""")
val pass = passRegex.find(srcresponse)?.groupValues?.get(1)?.replace(
Regex("""^//"""), "https://"
@@ -85,12 +85,18 @@ open class VidSrcExtractor : ExtractorApi() {
this.name,
this.name,
srcm3u8,
- "https://vidsrc.stream/",
+ this.mainUrl,
Qualities.Unknown.value,
extractorData = pass,
isM3u8 = true
)
)
+
+// M3u8Helper.generateM3u8(
+// name,
+// srcm3u8,
+// absoluteUrl
+// ).forEach(callback)
} else {
loadExtractor(linkfixed, url, subtitleCallback, callback)
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/VideoVard.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/VideoVard.kt
index 30a1d8fe..41e77967 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/VideoVard.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/VideoVard.kt
@@ -11,7 +11,7 @@ class VideovardSX : WcoStream() {
override var mainUrl = "https://videovard.sx"
}
-open class VideoVard : ExtractorApi() {
+class VideoVard : ExtractorApi() {
override var name = "Videovard" // Cause works for animekisa and wco
override var mainUrl = "https://videovard.to"
override val requiresReferer = false
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Vidmoly.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Vidmoly.kt
deleted file mode 100644
index 615cfd74..00000000
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Vidmoly.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-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.utils.*
-import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
-
-class Vidmolyme : Vidmoly() {
- override val mainUrl = "https://vidmoly.me"
-}
-
-open class Vidmoly : ExtractorApi() {
- override val name = "Vidmoly"
- override val mainUrl = "https://vidmoly.to"
- override val requiresReferer = true
-
- private fun String.addMarks(str: String): String {
- return this.replace(Regex("\"?$str\"?"), "\"$str\"")
- }
-
- override suspend fun getUrl(
- url: String,
- referer: String?,
- subtitleCallback: (SubtitleFile) -> Unit,
- callback: (ExtractorLink) -> Unit
- ) {
-
- val script = app.get(
- url,
- referer = referer,
- ).document.select("script")
- .find { it.data().contains("sources:") }?.data()
- val videoData = script?.substringAfter("sources: [")
- ?.substringBefore("],")?.addMarks("file")
- val subData = script?.substringAfter("tracks: [")?.substringBefore("]")?.addMarks("file")
- ?.addMarks("label")?.addMarks("kind")
-
- tryParseJson(videoData)?.file?.let { m3uLink ->
- M3u8Helper.generateM3u8(
- name,
- m3uLink,
- "$mainUrl/"
- ).forEach(callback)
- }
-
- tryParseJson>("[${subData}]")
- ?.filter { it.kind == "captions" }?.map {
- subtitleCallback.invoke(
- SubtitleFile(
- it.label.toString(),
- fixUrl(it.file.toString())
- )
- )
- }
-
- }
-
- private data class Source(
- @JsonProperty("file") val file: String? = null,
- )
-
- private data class SubSource(
- @JsonProperty("file") val file: String? = null,
- @JsonProperty("label") val label: String? = null,
- @JsonProperty("kind") val kind: String? = null,
- )
-
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Vido.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Vido.kt
deleted file mode 100644
index 67e59281..00000000
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Vido.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-package com.lagradost.cloudstream3.extractors
-import com.lagradost.cloudstream3.app
-import com.lagradost.cloudstream3.utils.ExtractorApi
-import com.lagradost.cloudstream3.utils.ExtractorLink
-import com.lagradost.cloudstream3.utils.Qualities
-import com.lagradost.cloudstream3.utils.getAndUnpack
-
-class Vido : ExtractorApi() {
- override var name = "Vido"
- override var mainUrl = "https://vido.lol"
- private val srcRegex = Regex("""sources:\s*\["(.*?)"\]""")
- override val requiresReferer = true
-
- override suspend fun getUrl(url: String, referer: String?): List? {
- val methode = app.get(url.replace("/e/", "/embed-")) // fix wiflix and mesfilms
- with(methode) {
- if (!methode.isSuccessful) return null
- //val quality = unpackedText.lowercase().substringAfter(" height=").substringBefore(" ").toIntOrNull()
- srcRegex.find(this.text)?.groupValues?.get(1)?.let { link ->
- return listOf(
- ExtractorLink(
- name,
- name,
- link,
- url,
- Qualities.Unknown.value,
- true,
- )
- )
- }
- }
- return null
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Vidstream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Vidstream.kt
index 7eb7fbac..1d853b2d 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Vidstream.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Vidstream.kt
@@ -1,7 +1,7 @@
package com.lagradost.cloudstream3.extractors
import com.lagradost.cloudstream3.SubtitleFile
-import com.lagradost.cloudstream3.amap
+import com.lagradost.cloudstream3.apmap
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.argamap
import com.lagradost.cloudstream3.utils.ExtractorLink
@@ -37,7 +37,7 @@ class Vidstream(val mainUrl: String) {
val extractorUrl = getExtractorUrl(id)
argamap(
{
- normalApis.amap { api ->
+ normalApis.apmap { api ->
val url = api.getExtractorUrl(id)
api.getSafeUrl(
url,
@@ -55,8 +55,8 @@ class Vidstream(val mainUrl: String) {
val qualityRegex = Regex("(\\d+)P")
//a[download]
- pageDoc.select(".dowload > a")?.amap { element ->
- val href = element.attr("href") ?: return@amap
+ pageDoc.select(".dowload > a")?.apmap { element ->
+ val href = element.attr("href") ?: return@apmap
val qual = if (element.text()
.contains("HDP")
) "1080" else qualityRegex.find(element.text())?.destructured?.component1()
@@ -87,7 +87,7 @@ class Vidstream(val mainUrl: String) {
//val name = element.text()
// Matches vidstream links with extractors
- extractorApis.filter { !it.requiresReferer || !isCasting }.amap { api ->
+ extractorApis.filter { !it.requiresReferer || !isCasting }.apmap { api ->
if (link.startsWith(api.mainUrl)) {
api.getSafeUrl(link, extractorUrl, subtitleCallback, callback)
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Voe.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Voe.kt
deleted file mode 100644
index 12a76a9b..00000000
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Voe.kt
+++ /dev/null
@@ -1,32 +0,0 @@
-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
-
-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 link = res.select("script").find { it.data().contains("const sources") }?.data()
- ?.substringAfter("\"hls\": \"")?.substringBefore("\",")
-
- M3u8Helper.generateM3u8(
- name,
- link ?: return,
- "$mainUrl/",
- headers = mapOf("Origin" to "$mainUrl/")
- ).forEach(callback)
-
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/VoeExtractor.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/VoeExtractor.kt
index ad3f0150..d2f3f832 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/VoeExtractor.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/VoeExtractor.kt
@@ -13,42 +13,39 @@ open class VoeExtractor : ExtractorApi() {
override val requiresReferer = false
private data class ResponseLinks(
- @JsonProperty("hls") val hls: String?,
- @JsonProperty("mp4") val mp4: String?,
+ @JsonProperty("hls") val url: String?,
@JsonProperty("video_height") val label: Int?
//val type: String // Mp4
)
override suspend fun getUrl(url: String, referer: String?): List {
- val html = app.get(url).text
- if (html.isNotBlank()) {
- val src = html.substringAfter("const sources =").substringBefore(";")
- // Remove last comma, it is not proper json otherwise
+ val extractedLinksList: MutableList = mutableListOf()
+ val doc = app.get(url).text
+ if (doc.isNotBlank()) {
+ val start = "const sources ="
+ var src = doc.substring(doc.indexOf(start))
+ src = src.substring(start.length, src.indexOf(";"))
.replace("0,", "0")
- // Make json use the proper quotes
- .replace("'", "\"")
-
+ .trim()
//Log.i(this.name, "Result => (src) ${src}")
- parseJson(src)?.let { voeLink ->
- //Log.i(this.name, "Result => (voeLink) ${voeLink}")
-
- // Always defaults to the hls link, but returns the mp4 if null
- val linkUrl = voeLink.hls ?: voeLink.mp4
- val linkLabel = voeLink.label?.toString() ?: ""
+ parseJson(src)?.let { voelink ->
+ //Log.i(this.name, "Result => (voelink) ${voelink}")
+ val linkUrl = voelink.url
+ val linkLabel = voelink.label?.toString() ?: ""
if (!linkUrl.isNullOrEmpty()) {
- return listOf(
+ extractedLinksList.add(
ExtractorLink(
name = this.name,
source = this.name,
url = linkUrl,
quality = getQualityFromName(linkLabel),
referer = url,
- isM3u8 = voeLink.hls != null
+ isM3u8 = true
)
)
}
}
}
- return emptyList()
+ return extractedLinksList
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/WcoStream.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/WcoStream.kt
index 6cc486cd..d99485ea 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/WcoStream.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/WcoStream.kt
@@ -53,12 +53,6 @@ class VizcloudSite : WcoStream() {
override var mainUrl = "https://vizcloud.site"
}
-class Mcloud : WcoStream() {
- override var name = "Mcloud"
- override var mainUrl = "https://mcloud.to"
- override val requiresReferer = true
-}
-
open class WcoStream : ExtractorApi() {
override var name = "VidStream" // Cause works for animekisa and wco
override var mainUrl = "https://vidstream.pro"
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/XStreamCdn.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/XStreamCdn.kt
index 15ff0436..9e3585ae 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/XStreamCdn.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/XStreamCdn.kt
@@ -1,23 +1,12 @@
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.utils.AppUtils.tryParseJson
+import com.lagradost.cloudstream3.utils.AppUtils
import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.getQualityFromName
-class Cdnplayer: XStreamCdn() {
- override val name: String = "Cdnplayer"
- override val mainUrl: String = "https://cdnplayer.online"
-}
-
-class Kotakajair: XStreamCdn() {
- override val name: String = "Kotakajair"
- override val mainUrl: String = "https://kotakajair.xyz"
-}
-
class FEnet: XStreamCdn() {
override val name: String = "FEnet"
override val mainUrl: String = "https://fembed.net"
@@ -70,67 +59,44 @@ open class XStreamCdn : ExtractorApi() {
//val type: String // Mp4
)
- private data class Player(
- @JsonProperty("poster_file") val poster_file: String? = null,
- )
-
private data class ResponseJson(
@JsonProperty("success") val success: Boolean,
- @JsonProperty("player") val player: Player? = null,
- @JsonProperty("data") val data: List?,
- @JsonProperty("captions") val captions: List?,
- )
-
- private data class Captions(
- @JsonProperty("id") val id: String,
- @JsonProperty("hash") val hash: String,
- @JsonProperty("language") val language: String,
- @JsonProperty("extension") val extension: String
+ @JsonProperty("data") val data: List?
)
override fun getExtractorUrl(id: String): String {
return "$domainUrl/api/source/$id"
}
- override suspend fun getUrl(
- url: String,
- referer: String?,
- subtitleCallback: (SubtitleFile) -> Unit,
- callback: (ExtractorLink) -> Unit
- ) {
+ override suspend fun getUrl(url: String, referer: String?): List {
val headers = mapOf(
"Referer" to url,
"User-Agent" to "Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0",
)
val id = url.trimEnd('/').split("/").last()
val newUrl = "https://${domainUrl}/api/source/${id}"
- app.post(newUrl, headers = headers).let { res ->
- val sources = tryParseJson(res.text)
- sources?.let {
+ val extractedLinksList: MutableList = mutableListOf()
+ with(app.post(newUrl, headers = headers)) {
+ if (this.code != 200) return listOf()
+ val text = this.text
+ if (text.isEmpty()) return listOf()
+ if (text == """{"success":false,"data":"Video not found or has been removed"}""") return listOf()
+ AppUtils.parseJson(text)?.let {
if (it.success && it.data != null) {
- it.data.map { source ->
- callback.invoke(
+ it.data.forEach { data ->
+ extractedLinksList.add(
ExtractorLink(
name,
name = name,
- source.file,
+ data.file,
url,
- getQualityFromName(source.label),
+ getQualityFromName(data.label),
)
)
}
}
}
-
- val userData = sources?.player?.poster_file?.split("/")?.get(2)
- sources?.captions?.map {
- subtitleCallback.invoke(
- SubtitleFile(
- it?.language.toString(),
- "$mainUrl/asset/userdata/$userData/caption/${it?.hash}/${it?.id}.${it?.extension}"
- )
- )
- }
}
+ return extractedLinksList
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/YourUpload.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/YourUpload.kt
index c7aa989d..3c564f67 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/YourUpload.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/YourUpload.kt
@@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.getQualityFromName
-open class YourUpload: ExtractorApi() {
+class YourUpload: ExtractorApi() {
override val name = "Yourupload"
override val mainUrl = "https://www.yourupload.com"
override val requiresReferer = false
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Zorofile.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Zorofile.kt
index 43c4eefb..84785b6c 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Zorofile.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Zorofile.kt
@@ -10,7 +10,7 @@ import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.M3u8Helper
-open class Zorofile : ExtractorApi() {
+class Zorofile : ExtractorApi() {
override val name = "Zorofile"
override val mainUrl = "https://zorofile.com"
override val requiresReferer = true
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Zplayer.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Zplayer.kt
index d17b427d..6108d2c5 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Zplayer.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/Zplayer.kt
@@ -1,6 +1,6 @@
package com.lagradost.cloudstream3.extractors
-import com.lagradost.cloudstream3.amap
+import com.lagradost.cloudstream3.apmap
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.ExtractorLink
@@ -36,7 +36,7 @@ open class ZplayerV2 : ExtractorApi() {
val m3u8regex = Regex("((https:|http:)\\/\\/.*\\.m3u8)")
m3u8regex.findAll(testdata).map {
it.value
- }.toList().amap { urlm3u8 ->
+ }.toList().apmap { urlm3u8 ->
if (urlm3u8.contains("m3u8")) {
val testurl = app.get(urlm3u8, headers = mapOf("Referer" to url)).text
if (testurl.contains("EXTM3U")) {
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/helper/AsianEmbedHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/helper/AsianEmbedHelper.kt
index 0b401c06..e70a9474 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/helper/AsianEmbedHelper.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/helper/AsianEmbedHelper.kt
@@ -2,7 +2,7 @@ package com.lagradost.cloudstream3.extractors.helper
import android.util.Log
import com.lagradost.cloudstream3.SubtitleFile
-import com.lagradost.cloudstream3.amap
+import com.lagradost.cloudstream3.apmap
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.loadExtractor
@@ -18,7 +18,7 @@ class AsianEmbedHelper {
val doc = app.get(url).document
val links = doc.select("div#list-server-more > ul > li.linkserver")
if (!links.isNullOrEmpty()) {
- links.amap {
+ links.apmap {
val datavid = it.attr("data-video") ?: ""
//Log.i("AsianEmbed", "Result => (datavid) ${datavid}")
if (datavid.isNotBlank()) {
diff --git a/app/src/main/java/com/lagradost/cloudstream3/metaproviders/AnilistRedirector.kt b/app/src/main/java/com/lagradost/cloudstream3/metaproviders/AnilistRedirector.kt
new file mode 100644
index 00000000..208db14b
--- /dev/null
+++ b/app/src/main/java/com/lagradost/cloudstream3/metaproviders/AnilistRedirector.kt
@@ -0,0 +1,30 @@
+package com.lagradost.cloudstream3.metaproviders
+
+import com.lagradost.cloudstream3.ErrorLoadingException
+import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.SyncApis
+import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi
+import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi
+import com.lagradost.cloudstream3.utils.SyncUtil
+
+object SyncRedirector {
+ val syncApis = SyncApis
+
+ suspend fun redirect(url: String, preferredUrl: String): String {
+ for (api in syncApis) {
+ if (url.contains(api.mainUrl)) {
+ val otherApi = when (api.name) {
+ aniListApi.name -> "anilist"
+ malApi.name -> "myanimelist"
+ else -> return url
+ }
+
+ return SyncUtil.getUrlsFromId(api.getIdFromUrl(url), otherApi).firstOrNull { realUrl ->
+ realUrl.contains(preferredUrl)
+ } ?: run {
+ throw ErrorLoadingException("Page does not exist on $preferredUrl")
+ }
+ }
+ }
+ return url
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/metaproviders/CrossTmdbProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/metaproviders/CrossTmdbProvider.kt
index 07aa904e..b01d188c 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/metaproviders/CrossTmdbProvider.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/metaproviders/CrossTmdbProvider.kt
@@ -39,7 +39,7 @@ class CrossTmdbProvider : TmdbProvider() {
): Boolean {
tryParseJson(data)?.let { metaData ->
if (!metaData.isSuccess) return false
- metaData.movies?.amap { (apiName, data) ->
+ metaData.movies?.apmap { (apiName, data) ->
getApiFromNameNull(apiName)?.let {
try {
it.loadLinks(data, isCasting, subtitleCallback, callback)
@@ -64,10 +64,10 @@ class CrossTmdbProvider : TmdbProvider() {
val matchName = filterName(this.name)
when (this) {
is MovieLoadResponse -> {
- val data = validApis.amap { api ->
+ val data = validApis.apmap { api ->
try {
if (api.supportedTypes.contains(TvType.Movie)) { //|| api.supportedTypes.contains(TvType.AnimeMovie)
- return@amap api.search(this.name)?.first {
+ return@apmap api.search(this.name)?.first {
if (filterName(it.name).equals(
matchName,
ignoreCase = true
diff --git a/app/src/main/java/com/lagradost/cloudstream3/metaproviders/MultiAnimeProvider.kt b/app/src/main/java/com/lagradost/cloudstream3/metaproviders/MultiAnimeProvider.kt
index e8ac1876..0ab44b68 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/metaproviders/MultiAnimeProvider.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/metaproviders/MultiAnimeProvider.kt
@@ -45,7 +45,7 @@ class MultiAnimeProvider : MainAPI() {
override suspend fun load(url: String): LoadResponse? {
return syncApi.getResult(url)?.let { res ->
- val data = SyncUtil.getUrlsFromId(res.id, syncUtilType).amap { url ->
+ val data = SyncUtil.getUrlsFromId(res.id, syncUtilType).apmap { url ->
validApis.firstOrNull { api -> url.startsWith(api.mainUrl) }?.load(url)
}.filterNotNull()
diff --git a/app/src/main/java/com/lagradost/cloudstream3/metaproviders/SyncRedirector.kt b/app/src/main/java/com/lagradost/cloudstream3/metaproviders/SyncRedirector.kt
deleted file mode 100644
index 75e96bec..00000000
--- a/app/src/main/java/com/lagradost/cloudstream3/metaproviders/SyncRedirector.kt
+++ /dev/null
@@ -1,56 +0,0 @@
-package com.lagradost.cloudstream3.metaproviders
-
-import com.lagradost.cloudstream3.MainAPI
-import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
-import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.SyncApis
-import com.lagradost.cloudstream3.syncproviders.SyncIdName
-
-object SyncRedirector {
- val syncApis = SyncApis
- private val syncIds =
- listOf(
- SyncIdName.MyAnimeList to Regex("""myanimelist\.net\/anime\/(\d+)"""),
- SyncIdName.Anilist to Regex("""anilist\.co\/anime\/(\d+)""")
- )
-
- suspend fun redirect(
- url: String,
- providerApi: MainAPI
- ): String {
- // Deprecated since providers should do this instead!
-
- // Tries built in ID -> ProviderUrl
- /*
- for (api in syncApis) {
- if (url.contains(api.mainUrl)) {
- val otherApi = when (api.name) {
- aniListApi.name -> "anilist"
- malApi.name -> "myanimelist"
- else -> return url
- }
-
- SyncUtil.getUrlsFromId(api.getIdFromUrl(url), otherApi).firstOrNull { realUrl ->
- realUrl.contains(providerApi.mainUrl)
- }?.let {
- return it
- }
-// ?: run {
-// throw ErrorLoadingException("Page does not exist on $preferredUrl")
-// }
- }
- }
- */
-
- // Tries provider solution
- // This goes through all sync ids and finds supported id by said provider
- return syncIds.firstNotNullOfOrNull { (syncName, syncRegex) ->
- if (providerApi.supportedSyncNames.contains(syncName)) {
- syncRegex.find(url)?.value?.let {
- suspendSafeApiCall {
- providerApi.getLoadUrl(syncName, it)
- }
- }
- } else null
- } ?: url
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt b/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt
index bb15bc85..e5c03d64 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt
@@ -53,10 +53,6 @@ fun LifecycleOwner.observe(liveData: LiveData, action: (t: T) -> Unit) {
liveData.observe(this) { it?.let { t -> action(t) } }
}
-fun LifecycleOwner.observeNullable(liveData: LiveData, action: (t: T) -> Unit) {
- liveData.observe(this) { action(it) }
-}
-
inline fun some(value: T?): Some {
return if (value == null) {
Some.None
@@ -121,21 +117,13 @@ suspend fun suspendSafeApiCall(apiCall: suspend () -> T): T? {
}
}
-fun Throwable.getAllMessages(): String {
- return (this.localizedMessage ?: "") + (this.cause?.getAllMessages()?.let { "\n$it" } ?: "")
-}
-
-fun Throwable.getStackTracePretty(showMessage: Boolean = true): String {
- val prefix = if (showMessage) this.localizedMessage?.let { "\n$it" } ?: "" else ""
- return prefix + this.stackTrace.joinToString(
- separator = "\n"
- ) {
- "${it.fileName} ${it.lineNumber}"
- }
-}
-
fun safeFail(throwable: Throwable): Resource {
- val stackTraceMsg = throwable.getStackTracePretty()
+ val stackTraceMsg =
+ (throwable.localizedMessage ?: "") + "\n\n" + throwable.stackTrace.joinToString(
+ separator = "\n"
+ ) {
+ "${it.fileName} ${it.lineNumber}"
+ }
return Resource.Failure(false, null, null, stackTraceMsg)
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/network/CloudflareKiller.kt b/app/src/main/java/com/lagradost/cloudstream3/network/CloudflareKiller.kt
index 6950d961..7dc8dba7 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/network/CloudflareKiller.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/network/CloudflareKiller.kt
@@ -5,7 +5,6 @@ import android.webkit.CookieManager
import androidx.annotation.AnyThread
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.mvvm.debugWarning
-import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.nicehttp.Requests.Companion.await
import com.lagradost.nicehttp.cookies
import kotlinx.coroutines.runBlocking
@@ -27,10 +26,7 @@ class CloudflareKiller : Interceptor {
init {
// Needs to clear cookies between sessions to generate new cookies.
- normalSafeApiCall {
- // This can throw an exception on unsupported devices :(
- CookieManager.getInstance().removeAllCookies(null)
- }
+ CookieManager.getInstance().removeAllCookies(null)
}
val savedCookies: MutableMap> = mutableMapOf()
@@ -39,7 +35,7 @@ class CloudflareKiller : Interceptor {
* Gets the headers with cookies, webview user agent included!
* */
fun getCookieHeaders(url: String): Headers {
- val userAgentHeaders = WebViewResolver.webViewUserAgent?.let {
+ val userAgentHeaders = WebViewResolver.webViewUserAgent?.let {
mapOf("user-agent" to it)
} ?: emptyMap()
@@ -64,9 +60,7 @@ class CloudflareKiller : Interceptor {
}
private fun getWebViewCookie(url: String): String? {
- return normalSafeApiCall {
- CookieManager.getInstance()?.getCookie(url)
- }
+ return CookieManager.getInstance()?.getCookie(url)
}
/**
diff --git a/app/src/main/java/com/lagradost/cloudstream3/network/DdosGuardKiller.kt b/app/src/main/java/com/lagradost/cloudstream3/network/DdosGuardKiller.kt
index b5783f78..dca3ee00 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/network/DdosGuardKiller.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/network/DdosGuardKiller.kt
@@ -2,7 +2,7 @@ package com.lagradost.cloudstream3.network
import androidx.annotation.AnyThread
import com.lagradost.cloudstream3.app
-import com.lagradost.nicehttp.Requests
+import com.lagradost.nicehttp.Requests.Companion.await
import com.lagradost.nicehttp.cookies
import kotlinx.coroutines.runBlocking
import okhttp3.Interceptor
@@ -41,8 +41,7 @@ class DdosGuardKiller(private val alwaysBypass: Boolean) : Interceptor {
savedCookiesMap[request.url.host]
// If no cookies are found fetch and save em.
?: (request.url.scheme + "://" + request.url.host + (ddosBypassPath ?: "")).let {
- // Somehow app.get fails
- Requests().get(it).cookies.also { cookies ->
+ app.get(it, cacheTime = 0).cookies.also { cookies ->
savedCookiesMap[request.url.host] = cookies
}
}
@@ -52,6 +51,6 @@ class DdosGuardKiller(private val alwaysBypass: Boolean) : Interceptor {
request.newBuilder()
.headers(headers)
.build()
- ).execute()
+ ).await()
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/network/RequestsHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/network/RequestsHelper.kt
index a1d84f6c..13299002 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/network/RequestsHelper.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/network/RequestsHelper.kt
@@ -4,19 +4,16 @@ import android.content.Context
import androidx.preference.PreferenceManager
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.USER_AGENT
-import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.nicehttp.Requests
import com.lagradost.nicehttp.ignoreAllSSLErrors
import okhttp3.Cache
import okhttp3.Headers
import okhttp3.Headers.Companion.toHeaders
import okhttp3.OkHttpClient
-import org.conscrypt.Conscrypt
import java.io.File
-import java.security.Security
+
fun Requests.initClient(context: Context): OkHttpClient {
- normalSafeApiCall { Security.insertProviderAt(Conscrypt.newProvider(), 1) }
val settingsManager = PreferenceManager.getDefaultSharedPreferences(context)
val dns = settingsManager.getInt(context.getString(R.string.dns_pref), 0)
baseClient = OkHttpClient.Builder()
diff --git a/app/src/main/java/com/lagradost/cloudstream3/network/WebViewResolver.kt b/app/src/main/java/com/lagradost/cloudstream3/network/WebViewResolver.kt
index 9171aed9..538b96f8 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/network/WebViewResolver.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/network/WebViewResolver.kt
@@ -7,12 +7,9 @@ import com.lagradost.cloudstream3.AcraApplication
import com.lagradost.cloudstream3.AcraApplication.Companion.context
import com.lagradost.cloudstream3.USER_AGENT
import com.lagradost.cloudstream3.app
-import com.lagradost.cloudstream3.mvvm.debugException
import com.lagradost.cloudstream3.mvvm.logError
-import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.utils.Coroutines.main
import com.lagradost.cloudstream3.utils.Coroutines.mainWork
-import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf
import com.lagradost.nicehttp.requestCreator
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
@@ -67,15 +64,9 @@ class WebViewResolver(
method: String = "GET",
requestCallBack: (Request) -> Boolean = { false },
): Pair> {
- return try {
- resolveUsingWebView(
- requestCreator(method, url, referer = referer), requestCallBack
- )
- } catch (e: java.lang.IllegalArgumentException) {
- logError(e)
- debugException { "ILLEGAL URL IN resolveUsingWebView!" }
- return null to emptyList()
- }
+ return resolveUsingWebView(
+ requestCreator(method, url, referer = referer), requestCallBack
+ )
}
/**
@@ -105,7 +96,7 @@ class WebViewResolver(
}
var fixedRequest: Request? = null
- val extraRequestList = threadSafeListOf()
+ val extraRequestList = mutableListOf()
main {
// Useful for debugging
@@ -137,7 +128,7 @@ class WebViewResolver(
println("Loading WebView URL: $webViewUrl")
if (interceptUrl.containsMatchIn(webViewUrl)) {
- fixedRequest = request.toRequest()?.also {
+ fixedRequest = request.toRequest().also {
requestCallBack(it)
}
println("Web-view request finished: $webViewUrl")
@@ -146,9 +137,9 @@ class WebViewResolver(
}
if (additionalUrls.any { it.containsMatchIn(webViewUrl) }) {
- request.toRequest()?.also {
+ extraRequestList.add(request.toRequest().also {
if (requestCallBack(it)) destroyWebView()
- }?.let(extraRequestList::add)
+ })
}
// Suppress image requests as we don't display them anywhere
@@ -259,19 +250,14 @@ class WebViewResolver(
}
-fun WebResourceRequest.toRequest(): Request? {
+fun WebResourceRequest.toRequest(): Request {
val webViewUrl = this.url.toString()
- // If invalid url then it can crash with
- // java.lang.IllegalArgumentException: Expected URL scheme 'http' or 'https' but was 'data'
- // At Request.Builder().url(addParamsToUrl(url, params))
- return normalSafeApiCall {
- requestCreator(
- this.method,
- webViewUrl,
- this.requestHeaders,
- )
- }
+ return requestCreator(
+ this.method,
+ webViewUrl,
+ this.requestHeaders,
+ )
}
fun Response.toWebResourceResponse(): WebResourceResponse {
diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/Plugin.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/Plugin.kt
index 242baf59..ec845d2c 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/plugins/Plugin.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/Plugin.kt
@@ -9,6 +9,8 @@ import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.extractorApis
import android.util.Log
import com.fasterxml.jackson.annotation.JsonProperty
+import com.lagradost.cloudstream3.utils.resources.ResourcePackManager
+import com.lagradost.cloudstream3.utils.resources.ResourcePatch
const val PLUGIN_TAG = "PluginInstance"
@@ -50,6 +52,16 @@ abstract class Plugin {
extractorApis.add(element)
}
+ /**
+ * Used to register a new resource pack
+ * @param factory The function that provided the original resources will generate a ResourcePatch instance
+ * You should probably use MapResourcePatch
+ * @see com.lagradost.cloudstream3.utils.resources.MapResourcePatch
+ */
+ fun registerResourcePack(name: String, factory: (Resources) -> ResourcePatch) {
+ ResourcePackManager.registerPack(name, factory, this.__filename)
+ }
+
class Manifest {
@JsonProperty("name") var name: String? = null
@JsonProperty("pluginClassName") var pluginClassName: String? = null
diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt
index 0dee57eb..3b55a99a 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt
@@ -1,45 +1,41 @@
package com.lagradost.cloudstream3.plugins
import android.app.*
-import android.content.Context
+import dalvik.system.PathClassLoader
+import com.google.gson.Gson
import android.content.res.AssetManager
import android.content.res.Resources
-import android.os.Build
import android.os.Environment
-import android.util.Log
import android.widget.Toast
+import android.content.Context
+import android.os.Build
+import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
-import androidx.fragment.app.FragmentActivity
import com.fasterxml.jackson.annotation.JsonProperty
-import com.google.gson.Gson
import com.lagradost.cloudstream3.*
-import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings
-import com.lagradost.cloudstream3.APIHolder.removePluginMapping
-import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
+import com.lagradost.cloudstream3.plugins.RepositoryManager.ONLINE_PLUGINS_FOLDER
+import com.lagradost.cloudstream3.plugins.RepositoryManager.downloadPluginToFile
import com.lagradost.cloudstream3.CommonActivity.showToast
-import com.lagradost.cloudstream3.MainAPI.Companion.settingsForProvider
+import com.lagradost.cloudstream3.plugins.RepositoryManager.getRepoPlugins
+import com.lagradost.cloudstream3.ui.settings.extensions.REPOSITORIES_KEY
+import com.lagradost.cloudstream3.ui.settings.extensions.RepositoryData
+import com.lagradost.cloudstream3.utils.VideoDownloadManager.sanitizeFilename
+import com.lagradost.cloudstream3.APIHolder.removePluginMapping
import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
import com.lagradost.cloudstream3.mvvm.debugPrint
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
-import com.lagradost.cloudstream3.plugins.RepositoryManager.ONLINE_PLUGINS_FOLDER
import com.lagradost.cloudstream3.plugins.RepositoryManager.PREBUILT_REPOSITORIES
-import com.lagradost.cloudstream3.plugins.RepositoryManager.downloadPluginToFile
-import com.lagradost.cloudstream3.plugins.RepositoryManager.getRepoPlugins
-import com.lagradost.cloudstream3.ui.result.UiText
-import com.lagradost.cloudstream3.ui.result.txt
-import com.lagradost.cloudstream3.ui.settings.extensions.REPOSITORIES_KEY
-import com.lagradost.cloudstream3.ui.settings.extensions.RepositoryData
+import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.Coroutines.main
import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
-import com.lagradost.cloudstream3.utils.VideoDownloadManager.sanitizeFilename
import com.lagradost.cloudstream3.utils.extractorApis
-import dalvik.system.PathClassLoader
+import com.lagradost.cloudstream3.utils.resources.ResourcePackManager
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.io.File
@@ -145,10 +141,8 @@ object PluginManager {
return getKey(PLUGINS_KEY_LOCAL) ?: emptyArray()
}
- private val CLOUD_STREAM_FOLDER =
- Environment.getExternalStorageDirectory().absolutePath + "/Cloudstream3/"
-
- private val LOCAL_PLUGINS_PATH = CLOUD_STREAM_FOLDER + "plugins"
+ private val LOCAL_PLUGINS_PATH =
+ Environment.getExternalStorageDirectory().absolutePath + "/Cloudstream3/plugins"
public var currentlyLoading: String? = null
@@ -166,11 +160,11 @@ object PluginManager {
private var loadedLocalPlugins = false
private val gson = Gson()
- private suspend fun maybeLoadPlugin(context: Context, file: File) {
+ private suspend fun maybeLoadPlugin(activity: Activity, file: File) {
val name = file.name
if (file.extension == "zip" || file.extension == "cs3") {
loadPlugin(
- context,
+ activity,
file,
PluginData(name, null, false, file.absolutePath, PLUGIN_VERSION_NOT_SET)
)
@@ -200,7 +194,7 @@ object PluginManager {
// var allCurrentOutDatedPlugins: Set = emptySet()
- suspend fun loadSinglePlugin(context: Context, apiName: String): Boolean {
+ suspend fun loadSinglePlugin(activity: Activity, apiName: String): Boolean {
return (getPluginsOnline().firstOrNull {
// Most of the time the provider ends with Provider which isn't part of the api name
it.internalName.replace("provider", "", ignoreCase = true) == apiName
@@ -210,7 +204,7 @@ object PluginManager {
})?.let { savedData ->
// OnlinePluginData(savedData, onlineData)
loadPlugin(
- context,
+ activity,
File(savedData.filePath),
savedData
)
@@ -227,7 +221,10 @@ object PluginManager {
fun updateAllOnlinePluginsAndLoadThem(activity: Activity) {
// Load all plugins as fast as possible!
loadAllOnlinePlugins(activity)
- afterPluginsLoadedEvent.invoke(false)
+
+ ioSafe {
+ afterPluginsLoadedEvent.invoke(true)
+ }
val urls = (getKey>(REPOSITORIES_KEY)
?: emptyArray()) + PREBUILT_REPOSITORIES
@@ -258,12 +255,11 @@ object PluginManager {
//updatedPlugins.add(activity.getString(R.string.single_plugin_disabled, pluginData.onlineData.second.name))
unloadPlugin(pluginData.savedData.filePath)
} else if (pluginData.isOutdated) {
- downloadPlugin(
+ downloadAndLoadPlugin(
activity,
pluginData.onlineData.second.url,
pluginData.savedData.internalName,
- File(pluginData.savedData.filePath),
- true
+ File(pluginData.savedData.filePath)
).let { success ->
if (success)
updatedPlugins.add(pluginData.onlineData.second.name)
@@ -272,134 +268,31 @@ object PluginManager {
}
main {
- val uitext = txt(R.string.plugins_updated, updatedPlugins.size)
- createNotification(activity, uitext, updatedPlugins)
+ createNotification(activity, updatedPlugins)
}
- // ioSafe {
- afterPluginsLoadedEvent.invoke(false)
- // }
+ ioSafe {
+ afterPluginsLoadedEvent.invoke(true)
+ }
Log.i(TAG, "Plugin update done!")
}
- /**
- * Automatically download plugins not yet existing on local
- * 1. Gets all online data from online plugins repo
- * 2. Fetch all not downloaded plugins
- * 3. Download them and reload plugins
- **/
- fun downloadNotExistingPluginsAndLoad(activity: Activity) {
- val newDownloadPlugins = mutableListOf()
- val urls = (getKey>(REPOSITORIES_KEY)
- ?: emptyArray()) + PREBUILT_REPOSITORIES
- val onlinePlugins = urls.toList().apmap {
- getRepoPlugins(it.url)?.toList() ?: emptyList()
- }.flatten().distinctBy { it.second.url }
-
- val providerLang = activity.getApiProviderLangSettings()
- //Log.i(TAG, "providerLang => ${providerLang.toJson()}")
-
- // Iterate online repos and returns not downloaded plugins
- val notDownloadedPlugins = onlinePlugins.mapNotNull { onlineData ->
- val sitePlugin = onlineData.second
- //Don't include empty urls
- if (sitePlugin.url.isBlank()) {
- return@mapNotNull null
- }
- if (sitePlugin.repositoryUrl.isNullOrBlank()) {
- return@mapNotNull null
- }
-
- //Omit already existing plugins
- if (getPluginPath(activity, sitePlugin.internalName, onlineData.first).exists()) {
- Log.i(TAG, "Skip > ${sitePlugin.internalName}")
- return@mapNotNull null
- }
-
- //Omit lang not selected on language setting
- val lang = sitePlugin.language ?: return@mapNotNull null
- //If set to 'universal', don't skip any language
- if (!providerLang.contains(AllLanguagesName) && !providerLang.contains(lang)) {
- return@mapNotNull null
- }
- //Log.i(TAG, "sitePlugin lang => $lang")
-
- //Omit NSFW, if disabled
- sitePlugin.tvTypes?.let { tvtypes ->
- if (!settingsForProvider.enableAdult) {
- if (tvtypes.contains(TvType.NSFW.name)) {
- return@mapNotNull null
- }
- }
- }
- val savedData = PluginData(
- url = sitePlugin.url,
- internalName = sitePlugin.internalName,
- isOnline = true,
- filePath = "",
- version = sitePlugin.version
- )
- OnlinePluginData(savedData, onlineData)
- }
- //Log.i(TAG, "notDownloadedPlugins => ${notDownloadedPlugins.toJson()}")
-
- notDownloadedPlugins.apmap { pluginData ->
- downloadPlugin(
- activity,
- pluginData.onlineData.second.url,
- pluginData.savedData.internalName,
- pluginData.onlineData.first,
- !pluginData.isDisabled
- ).let { success ->
- if (success)
- newDownloadPlugins.add(pluginData.onlineData.second.name)
- }
- }
-
- main {
- val uitext = txt(R.string.plugins_downloaded, newDownloadPlugins.size)
- createNotification(activity, uitext, newDownloadPlugins)
- }
-
- // ioSafe {
- afterPluginsLoadedEvent.invoke(false)
- // }
-
- Log.i(TAG, "Plugin download done!")
- }
-
/**
* Use updateAllOnlinePluginsAndLoadThem
* */
- fun loadAllOnlinePlugins(context: Context) {
+ fun loadAllOnlinePlugins(activity: Activity) {
// Load all plugins as fast as possible!
(getPluginsOnline()).toList().apmap { pluginData ->
loadPlugin(
- context,
+ activity,
File(pluginData.filePath),
pluginData
)
}
}
- /**
- * Reloads all local plugins and forces a page update, used for hot reloading with deployWithAdb
- **/
- fun hotReloadAllLocalPlugins(activity: FragmentActivity?) {
- Log.d(TAG, "Reloading all local plugins!")
- if (activity == null) return
- getPluginsLocal().forEach {
- unloadPlugin(it.filePath)
- }
- loadAllLocalPlugins(activity, true)
- }
-
- /**
- * @param forceReload see afterPluginsLoadedEvent, basically a way to load all local plugins
- * and reload all pages even if they are previously valid
- **/
- fun loadAllLocalPlugins(context: Context, forceReload: Boolean) {
+ fun loadAllLocalPlugins(activity: Activity) {
val dir = File(LOCAL_PLUGINS_PATH)
removeKey(PLUGINS_KEY_LOCAL)
@@ -417,39 +310,24 @@ object PluginManager {
Log.d(TAG, "Files in '${LOCAL_PLUGINS_PATH}' folder: $sortedPlugins")
sortedPlugins?.sortedBy { it.name }?.apmap { file ->
- maybeLoadPlugin(context, file)
+ maybeLoadPlugin(activity, file)
}
loadedLocalPlugins = true
- afterPluginsLoadedEvent.invoke(forceReload)
- }
-
- /**
- * This can be used to override any extension loading to fix crashes!
- * @return true if safe mode file is present
- **/
- fun checkSafeModeFile(): Boolean {
- return normalSafeApiCall {
- val folder = File(CLOUD_STREAM_FOLDER)
- if (!folder.exists()) return@normalSafeApiCall false
- val files = folder.listFiles { _, name ->
- name.equals("safe", ignoreCase = true)
- }
- files?.any()
- } ?: false
+ afterPluginsLoadedEvent.invoke(true)
}
/**
* @return True if successful, false if not
* */
- private suspend fun loadPlugin(context: Context, file: File, data: PluginData): Boolean {
+ private suspend fun loadPlugin(activity: Activity, file: File, data: PluginData): Boolean {
val fileName = file.nameWithoutExtension
val filePath = file.absolutePath
currentlyLoading = fileName
Log.i(TAG, "Loading plugin: $data")
return try {
- val loader = PathClassLoader(filePath, context.classLoader)
+ val loader = PathClassLoader(filePath, activity.classLoader)
var manifest: Plugin.Manifest
loader.getResourceAsStream("manifest.json").use { stream ->
if (stream == null) {
@@ -493,22 +371,22 @@ object PluginManager {
addAssetPath.invoke(assets, file.absolutePath)
pluginInstance.resources = Resources(
assets,
- context.resources.displayMetrics,
- context.resources.configuration
+ activity.resources.displayMetrics,
+ activity.resources.configuration
)
}
plugins[filePath] = pluginInstance
classLoaders[loader] = pluginInstance
urlPlugins[data.url ?: filePath] = pluginInstance
- pluginInstance.load(context)
+ pluginInstance.load(activity)
Log.i(TAG, "Loaded plugin ${data.internalName} successfully")
currentlyLoading = null
true
} catch (e: Throwable) {
Log.e(TAG, "Failed to load $file: ${Log.getStackTraceString(e)}")
showToast(
- context.getActivity(),
- context.getString(R.string.plugin_load_fail).format(fileName),
+ activity,
+ activity.getString(R.string.plugin_load_fail).format(fileName),
Toast.LENGTH_LONG
)
currentlyLoading = null
@@ -516,7 +394,7 @@ object PluginManager {
}
}
- fun unloadPlugin(absolutePath: String) {
+ private fun unloadPlugin(absolutePath: String) {
Log.i(TAG, "Unloading plugin: $absolutePath")
val plugin = plugins[absolutePath]
if (plugin == null) {
@@ -536,6 +414,7 @@ object PluginManager {
}
APIHolder.allProviders.removeIf { provider: MainAPI -> provider.sourcePlugin == plugin.__filename }
extractorApis.removeIf { provider: ExtractorApi -> provider.sourcePlugin == plugin.__filename }
+ ResourcePackManager.unregisterPlugin(plugin.__filename)
classLoaders.values.removeIf { v -> v == plugin }
@@ -567,48 +446,49 @@ object PluginManager {
return File("${context.filesDir}/${ONLINE_PLUGINS_FOLDER}/${folderName}/$fileName.cs3")
}
- suspend fun downloadPlugin(
+ /**
+ * Used for fresh installs
+ * */
+ suspend fun downloadAndLoadPlugin(
activity: Activity,
pluginUrl: String,
internalName: String,
- repositoryUrl: String,
- loadPlugin: Boolean
+ repositoryUrl: String
): Boolean {
val file = getPluginPath(activity, internalName, repositoryUrl)
- return downloadPlugin(activity, pluginUrl, internalName, file, loadPlugin)
+ downloadAndLoadPlugin(activity, pluginUrl, internalName, file)
+ return true
}
- suspend fun downloadPlugin(
+ /**
+ * Used for updates.
+ *
+ * Uses a file instead of repository url, as extensions can get moved it is better to directly
+ * update the files instead of getting the filepath from repo url.
+ * */
+ private suspend fun downloadAndLoadPlugin(
activity: Activity,
pluginUrl: String,
internalName: String,
file: File,
- loadPlugin: Boolean
): Boolean {
try {
+ unloadPlugin(file.absolutePath)
+
Log.d(TAG, "Downloading plugin: $pluginUrl to ${file.absolutePath}")
// The plugin file needs to be salted with the repository url hash as to allow multiple repositories with the same internal plugin names
- val newFile = downloadPluginToFile(pluginUrl, file) ?: return false
-
- val data = PluginData(
- internalName,
- pluginUrl,
- true,
- newFile.absolutePath,
- PLUGIN_VERSION_NOT_SET
- )
-
- return if (loadPlugin) {
- unloadPlugin(file.absolutePath)
- loadPlugin(
- activity,
- newFile,
- data
+ val newFile = downloadPluginToFile(pluginUrl, file)
+ return loadPlugin(
+ activity,
+ newFile ?: return false,
+ PluginData(
+ internalName,
+ pluginUrl,
+ true,
+ newFile.absolutePath,
+ PLUGIN_VERSION_NOT_SET
)
- } else {
- setPluginData(data)
- true
- }
+ )
} catch (e: Exception) {
logError(e)
return false
@@ -616,8 +496,7 @@ object PluginManager {
}
suspend fun deletePlugin(file: File): Boolean {
- val list =
- (getPluginsLocal() + getPluginsOnline()).filter { it.filePath == file.absolutePath }
+ val list = (getPluginsLocal() + getPluginsOnline()).filter { it.filePath == file.absolutePath }
return try {
if (File(file.absolutePath).delete()) {
@@ -652,14 +531,12 @@ object PluginManager {
private fun createNotification(
context: Context,
- uitext: UiText,
- extensions: List
+ extensionNames: List
): Notification? {
try {
+ if (extensionNames.isEmpty()) return null
- if (extensions.isEmpty()) return null
-
- val content = extensions.joinToString(", ")
+ val content = extensionNames.joinToString(", ")
// main { // DON'T WANT TO SLOW IT DOWN
val builder = NotificationCompat.Builder(context, EXTENSIONS_CHANNEL_ID)
.setAutoCancel(false)
@@ -668,8 +545,7 @@ object PluginManager {
.setSilent(true)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setColor(context.colorFromAttribute(R.attr.colorPrimary))
- .setContentTitle(uitext.asString(context))
- //.setContentTitle(context.getString(title, extensionNames.size))
+ .setContentTitle(context.getString(R.string.plugins_updated, extensionNames.size))
.setSmallIcon(R.drawable.ic_baseline_extension_24)
.setStyle(
NotificationCompat.BigTextStyle()
diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt
index 742bf308..e3203787 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt
@@ -2,17 +2,13 @@ package com.lagradost.cloudstream3.plugins
import android.content.Context
import com.fasterxml.jackson.annotation.JsonProperty
-import com.lagradost.cloudstream3.AcraApplication.Companion.context
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
-import com.lagradost.cloudstream3.R
-import com.lagradost.cloudstream3.amap
+import com.lagradost.cloudstream3.apmap
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.mvvm.logError
-import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
import com.lagradost.cloudstream3.plugins.PluginManager.getPluginSanitizedFileName
-import com.lagradost.cloudstream3.plugins.PluginManager.unloadPlugin
import com.lagradost.cloudstream3.ui.settings.extensions.REPOSITORIES_KEY
import com.lagradost.cloudstream3.ui.settings.extensions.RepositoryData
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
@@ -73,54 +69,18 @@ object RepositoryManager {
val PREBUILT_REPOSITORIES: Array by lazy {
getKey("PREBUILT_REPOSITORIES") ?: emptyArray()
}
- val GH_REGEX = Regex("^https://raw.githubusercontent.com/([A-Za-z0-9-]+)/([A-Za-z0-9_.-]+)/(.*)$")
-
- /* Convert raw.githubusercontent.com urls to cdn.jsdelivr.net if enabled in settings */
- fun convertRawGitUrl(url: String): String {
- if (getKey(context!!.getString(R.string.jsdelivr_proxy_key)) != true) return url
- val match = GH_REGEX.find(url) ?: return url
- val (user, repo, rest) = match.destructured
- return "https://cdn.jsdelivr.net/gh/$user/$repo@$rest"
- }
-
- suspend fun parseRepoUrl(url: String): String? {
- val fixedUrl = url.trim()
- return if (fixedUrl.contains("^https?://".toRegex())) {
- fixedUrl
- } else if (fixedUrl.contains("^(cloudstreamrepo://)|(https://cs\\.repo/\\??)".toRegex())) {
- fixedUrl.replace("^(cloudstreamrepo://)|(https://cs\\.repo/\\??)".toRegex(), "").let {
- return@let if (!it.contains("^https?://".toRegex()))
- "https://${it}"
- else fixedUrl
- }
- } else if (fixedUrl.matches("^[a-zA-Z0-9!_-]+$".toRegex())) {
- suspendSafeApiCall {
- app.get("https://l.cloudstream.cf/${fixedUrl}", allowRedirects = false).let {
- it.headers["Location"]?.let { url ->
- return@suspendSafeApiCall if (!url.startsWith("https://cutt.ly/branded-domains")) url
- else null
- }
- app.get("https://cutt.ly/${fixedUrl}", allowRedirects = false).let { it2 ->
- it2.headers["Location"]?.let { url ->
- return@suspendSafeApiCall if (url.startsWith("https://cutt.ly/404")) url else null
- }
- }
- }
- }
- } else null
- }
suspend fun parseRepository(url: String): Repository? {
return suspendSafeApiCall {
// Take manifestVersion and such into account later
- app.get(convertRawGitUrl(url)).parsedSafe()
+ app.get(url).parsedSafe()
}
}
private suspend fun parsePlugins(pluginUrls: String): List {
// Take manifestVersion and such into account later
return try {
- val response = app.get(convertRawGitUrl(pluginUrls))
+ val response = app.get(pluginUrls)
// Normal parsed function not working?
// return response.parsedSafe()
tryParseJson>(response.text)?.toList() ?: emptyList()
@@ -135,7 +95,7 @@ object RepositoryManager {
* */
suspend fun getRepoPlugins(repositoryUrl: String): List>? {
val repo = parseRepository(repositoryUrl) ?: return null
- return repo.pluginLists.amap { url ->
+ return repo.pluginLists.apmap { url ->
parsePlugins(url).map {
repositoryUrl to it
}
@@ -150,17 +110,43 @@ object RepositoryManager {
file.mkdirs()
// Overwrite if exists
- if (file.exists()) {
- file.delete()
- }
+ if (file.exists()) { file.delete() }
file.createNewFile()
- val body = app.get(convertRawGitUrl(pluginUrl)).okhttpResponse.body
+ val body = app.get(pluginUrl).okhttpResponse.body
write(body.byteStream(), file.outputStream())
file
}
}
+ suspend fun downloadPluginToFile(
+ context: Context,
+ pluginUrl: String,
+ /** Filename without .cs3 */
+ fileName: String,
+ folder: String
+ ): File? {
+ return suspendSafeApiCall {
+ val extensionsDir = File(context.filesDir, ONLINE_PLUGINS_FOLDER)
+ if (!extensionsDir.exists())
+ extensionsDir.mkdirs()
+
+ val newDir = File(extensionsDir, folder)
+ newDir.mkdirs()
+
+ val newFile = File(newDir, "${fileName}.cs3")
+ // Overwrite if exists
+ if (newFile.exists()) {
+ newFile.delete()
+ }
+ newFile.createNewFile()
+
+ val body = app.get(pluginUrl).okhttpResponse.body
+ write(body.byteStream(), newFile.outputStream())
+ newFile
+ }
+ }
+
fun getRepositories(): Array {
return getKey(REPOSITORIES_KEY) ?: emptyArray()
}
@@ -192,17 +178,9 @@ object RepositoryManager {
extensionsDir,
getPluginSanitizedFileName(repository.url)
)
-
- // Unload all plugins, not using deletePlugin since we
- // delete all data and files in deleteRepositoryData
- normalSafeApiCall {
- file.listFiles { plugin: File ->
- unloadPlugin(plugin.absolutePath)
- false
- }
- }
-
PluginManager.deleteRepositoryData(file.absolutePath)
+
+ file.delete()
}
private fun write(stream: InputStream, output: OutputStream) {
@@ -213,4 +191,4 @@ object RepositoryManager {
output.write(dataBuffer, 0, readBytes)
}
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/services/SubscriptionWorkManager.kt b/app/src/main/java/com/lagradost/cloudstream3/services/SubscriptionWorkManager.kt
deleted file mode 100644
index adf5abfa..00000000
--- a/app/src/main/java/com/lagradost/cloudstream3/services/SubscriptionWorkManager.kt
+++ /dev/null
@@ -1,224 +0,0 @@
-package com.lagradost.cloudstream3.services
-
-import android.app.NotificationManager
-import android.app.PendingIntent
-import android.content.Context
-import android.content.Intent
-import android.os.Build
-import androidx.core.app.NotificationCompat
-import androidx.core.net.toUri
-import androidx.work.*
-import com.lagradost.cloudstream3.*
-import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings
-import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
-import com.lagradost.cloudstream3.R
-import com.lagradost.cloudstream3.mvvm.safeApiCall
-import com.lagradost.cloudstream3.plugins.PluginManager
-import com.lagradost.cloudstream3.ui.result.txt
-import com.lagradost.cloudstream3.utils.AppUtils.createNotificationChannel
-import com.lagradost.cloudstream3.utils.Coroutines.ioWork
-import com.lagradost.cloudstream3.utils.DataStoreHelper
-import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllSubscriptions
-import com.lagradost.cloudstream3.utils.DataStoreHelper.getDub
-import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
-import com.lagradost.cloudstream3.utils.VideoDownloadManager.getImageBitmapFromUrl
-import kotlinx.coroutines.withTimeoutOrNull
-import java.util.concurrent.TimeUnit
-
-const val SUBSCRIPTION_CHANNEL_ID = "cloudstream3.subscriptions"
-const val SUBSCRIPTION_WORK_NAME = "work_subscription"
-const val SUBSCRIPTION_CHANNEL_NAME = "Subscriptions"
-const val SUBSCRIPTION_CHANNEL_DESCRIPTION = "Notifications for new episodes on subscribed shows"
-const val SUBSCRIPTION_NOTIFICATION_ID = 938712897 // Random unique
-
-class SubscriptionWorkManager(val context: Context, workerParams: WorkerParameters) :
- CoroutineWorker(context, workerParams) {
- companion object {
- fun enqueuePeriodicWork(context: Context?) {
- if (context == null) return
-
- val constraints = Constraints.Builder()
- .setRequiredNetworkType(NetworkType.CONNECTED)
- .build()
-
- val periodicSyncDataWork =
- PeriodicWorkRequest.Builder(SubscriptionWorkManager::class.java, 6, TimeUnit.HOURS)
- .addTag(SUBSCRIPTION_WORK_NAME)
- .setConstraints(constraints)
- .build()
-
- WorkManager.getInstance(context).enqueueUniquePeriodicWork(
- SUBSCRIPTION_WORK_NAME,
- ExistingPeriodicWorkPolicy.KEEP,
- periodicSyncDataWork
- )
-
- // Uncomment below for testing
-
-// val oneTimeSyncDataWork =
-// OneTimeWorkRequest.Builder(SubscriptionWorkManager::class.java)
-// .addTag(SUBSCRIPTION_WORK_NAME)
-// .setConstraints(constraints)
-// .build()
-//
-// WorkManager.getInstance(context).enqueue(oneTimeSyncDataWork)
- }
- }
-
- private val progressNotificationBuilder =
- NotificationCompat.Builder(context, SUBSCRIPTION_CHANNEL_ID)
- .setAutoCancel(false)
- .setColorized(true)
- .setOnlyAlertOnce(true)
- .setSilent(true)
- .setPriority(NotificationCompat.PRIORITY_DEFAULT)
- .setColor(context.colorFromAttribute(R.attr.colorPrimary))
- .setContentTitle(context.getString(R.string.subscription_in_progress_notification))
- .setSmallIcon(R.drawable.quantum_ic_refresh_white_24)
- .setProgress(0, 0, true)
-
- private val updateNotificationBuilder =
- NotificationCompat.Builder(context, SUBSCRIPTION_CHANNEL_ID)
- .setColorized(true)
- .setOnlyAlertOnce(true)
- .setAutoCancel(true)
- .setPriority(NotificationCompat.PRIORITY_DEFAULT)
- .setColor(context.colorFromAttribute(R.attr.colorPrimary))
- .setSmallIcon(R.drawable.ic_cloudstream_monochrome_big)
-
- private val notificationManager: NotificationManager =
- context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
-
- private fun updateProgress(max: Int, progress: Int, indeterminate: Boolean) {
- notificationManager.notify(
- SUBSCRIPTION_NOTIFICATION_ID, progressNotificationBuilder
- .setProgress(max, progress, indeterminate)
- .build()
- )
- }
-
- override suspend fun doWork(): Result {
-// println("Update subscriptions!")
- context.createNotificationChannel(
- SUBSCRIPTION_CHANNEL_ID,
- SUBSCRIPTION_CHANNEL_NAME,
- SUBSCRIPTION_CHANNEL_DESCRIPTION
- )
-
- setForeground(
- ForegroundInfo(
- SUBSCRIPTION_NOTIFICATION_ID,
- progressNotificationBuilder.build()
- )
- )
-
- val subscriptions = getAllSubscriptions()
-
- if (subscriptions.isEmpty()) {
- WorkManager.getInstance(context).cancelWorkById(this.id)
- return Result.success()
- }
-
- val max = subscriptions.size
- var progress = 0
-
- updateProgress(max, progress, true)
-
- // We need all plugins loaded.
- PluginManager.loadAllOnlinePlugins(context)
- PluginManager.loadAllLocalPlugins(context, false)
-
- subscriptions.apmap { savedData ->
- try {
- val id = savedData.id ?: return@apmap null
- val api = getApiFromNameNull(savedData.apiName) ?: return@apmap null
-
- // Reasonable timeout to prevent having this worker run forever.
- val response = withTimeoutOrNull(60_000) {
- api.load(savedData.url) as? EpisodeResponse
- } ?: return@apmap null
-
- val dubPreference =
- getDub(id) ?: if (
- context.getApiDubstatusSettings().contains(DubStatus.Dubbed)
- ) {
- DubStatus.Dubbed
- } else {
- DubStatus.Subbed
- }
-
- val latestEpisodes = response.getLatestEpisodes()
- val latestPreferredEpisode = latestEpisodes[dubPreference]
-
- val (shouldUpdate, latestEpisode) = if (latestPreferredEpisode != null) {
- val latestSeenEpisode =
- savedData.lastSeenEpisodeCount[dubPreference] ?: Int.MIN_VALUE
- val shouldUpdate = latestPreferredEpisode > latestSeenEpisode
- shouldUpdate to latestPreferredEpisode
- } else {
- val latestEpisode = latestEpisodes[DubStatus.None] ?: Int.MIN_VALUE
- val latestSeenEpisode =
- savedData.lastSeenEpisodeCount[DubStatus.None] ?: Int.MIN_VALUE
- val shouldUpdate = latestEpisode > latestSeenEpisode
- shouldUpdate to latestEpisode
- }
-
- DataStoreHelper.updateSubscribedData(
- id,
- savedData,
- response
- )
-
- if (shouldUpdate) {
- val updateHeader = savedData.name
- val updateDescription = txt(
- R.string.subscription_episode_released,
- latestEpisode,
- savedData.name
- ).asString(context)
-
- val intent = Intent(context, MainActivity::class.java).apply {
- data = savedData.url.toUri()
- flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
- }
-
- val pendingIntent =
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- PendingIntent.getActivity(
- context,
- 0,
- intent,
- PendingIntent.FLAG_IMMUTABLE
- )
- } else {
- PendingIntent.getActivity(context, 0, intent, 0)
- }
-
- val poster = ioWork {
- savedData.posterUrl?.let { url ->
- context.getImageBitmapFromUrl(
- url,
- savedData.posterHeaders
- )
- }
- }
-
- val updateNotification =
- updateNotificationBuilder.setContentTitle(updateHeader)
- .setContentText(updateDescription)
- .setContentIntent(pendingIntent)
- .setLargeIcon(poster)
- .build()
-
- notificationManager.notify(id, updateNotification)
- }
-
- // You can probably get some issues here since this is async but it does not matter much.
- updateProgress(max, ++progress, false)
- } catch (_: Throwable) {
- }
- }
-
- return Result.success()
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/services/VideoDownloadService.kt b/app/src/main/java/com/lagradost/cloudstream3/services/VideoDownloadService.kt
index 6151a0ed..be2fe75b 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/services/VideoDownloadService.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/services/VideoDownloadService.kt
@@ -1,22 +1,11 @@
package com.lagradost.cloudstream3.services
-import android.app.Service
+
+import android.app.IntentService
import android.content.Intent
-import android.os.IBinder
import com.lagradost.cloudstream3.utils.VideoDownloadManager
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.launch
-class VideoDownloadService : Service() {
-
- private val downloadScope = CoroutineScope(Dispatchers.Default)
-
- override fun onBind(intent: Intent?): IBinder? {
- return null
- }
-
- override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+class VideoDownloadService : IntentService("VideoDownloadService") {
+ override fun onHandleIntent(intent: Intent?) {
if (intent != null) {
val id = intent.getIntExtra("id", -1)
val type = intent.getStringExtra("type")
@@ -25,36 +14,10 @@ class VideoDownloadService : Service() {
"resume" -> VideoDownloadManager.DownloadActionType.Resume
"pause" -> VideoDownloadManager.DownloadActionType.Pause
"stop" -> VideoDownloadManager.DownloadActionType.Stop
- else -> return START_NOT_STICKY
- }
-
- downloadScope.launch {
- VideoDownloadManager.downloadEvent.invoke(Pair(id, state))
+ else -> return
}
+ VideoDownloadManager.downloadEvent.invoke(Pair(id, state))
}
}
-
- return START_NOT_STICKY
}
-
- override fun onDestroy() {
- downloadScope.coroutineContext.cancel()
- super.onDestroy()
- }
-}
-// override fun onHandleIntent(intent: Intent?) {
-// if (intent != null) {
-// val id = intent.getIntExtra("id", -1)
-// val type = intent.getStringExtra("type")
-// if (id != -1 && type != null) {
-// val state = when (type) {
-// "resume" -> VideoDownloadManager.DownloadActionType.Resume
-// "pause" -> VideoDownloadManager.DownloadActionType.Pause
-// "stop" -> VideoDownloadManager.DownloadActionType.Stop
-// else -> return
-// }
-// VideoDownloadManager.downloadEvent.invoke(Pair(id, state))
-// }
-// }
-// }
-//}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AccountManager.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AccountManager.kt
index 8ce6bae2..825ff673 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AccountManager.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AccountManager.kt
@@ -12,8 +12,6 @@ abstract class AccountManager(private val defIndex: Int) : AuthAPI {
val aniListApi = AniListApi(0)
val openSubtitlesApi = OpenSubtitlesApi(0)
val indexSubtitlesApi = IndexSubtitleApi()
- val addic7ed = Addic7ed()
- val localListApi = LocalList()
// used to login via app intent
val OAuth2Apis
@@ -30,7 +28,7 @@ abstract class AccountManager(private val defIndex: Int) : AuthAPI {
// used for active syncing
val SyncApis
get() = listOf(
- SyncRepo(malApi), SyncRepo(aniListApi), SyncRepo(localListApi)
+ SyncRepo(malApi), SyncRepo(aniListApi)
)
val inAppAuths
@@ -39,19 +37,11 @@ abstract class AccountManager(private val defIndex: Int) : AuthAPI {
val subtitleProviders
get() = listOf(
openSubtitlesApi,
- indexSubtitlesApi, // they got anti scraping measures in place :(
- addic7ed
+ indexSubtitlesApi // they got anti scraping measures in place :(
)
const val appString = "cloudstreamapp"
const val appStringRepo = "cloudstreamrepo"
- const val appStringPlayer = "cloudstreamplayer"
-
- // Instantly start the search given a query
- const val appStringSearch = "cloudstreamsearch"
-
- // Instantly resume watching a show
- const val appStringResumeWatching = "cloudstreamcontinuewatching"
val unixTime: Long
get() = System.currentTimeMillis() / 1000L
diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncAPI.kt
index 8c76c5bf..5aa56a02 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncAPI.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncAPI.kt
@@ -1,31 +1,10 @@
package com.lagradost.cloudstream3.syncproviders
import com.lagradost.cloudstream3.*
-import com.lagradost.cloudstream3.ui.library.ListSorting
-import com.lagradost.cloudstream3.ui.result.UiText
-import me.xdrop.fuzzywuzzy.FuzzySearch
-
-enum class SyncIdName {
- Anilist,
- MyAnimeList,
- Trakt,
- Imdb,
- LocalList
-}
interface SyncAPI : OAuth2API {
- /**
- * Set this to true if the user updates something on the list like watch status or score
- **/
- var requireLibraryRefresh: Boolean
val mainUrl: String
- /**
- * Allows certain providers to open pages from
- * library links.
- **/
- val syncIdName: SyncIdName
-
/**
-1 -> None
0 -> Watching
@@ -43,9 +22,7 @@ interface SyncAPI : OAuth2API {
suspend fun search(name: String): List?
- suspend fun getPersonalLibrary(): LibraryMetadata?
-
- fun getIdFromUrl(url: String): String
+ fun getIdFromUrl(url : String) : String
data class SyncSearchResult(
override val name: String,
@@ -65,7 +42,7 @@ interface SyncAPI : OAuth2API {
val score: Int?,
val watchedEpisodes: Int?,
var isFavorite: Boolean? = null,
- var maxEpisodes: Int? = null,
+ var maxEpisodes : Int? = null,
)
data class SyncResult(
@@ -86,9 +63,9 @@ interface SyncAPI : OAuth2API {
var genres: List? = null,
var synonyms: List? = null,
var trailers: List? = null,
- var isAdult: Boolean? = null,
+ var isAdult : Boolean? = null,
var posterUrl: String? = null,
- var backgroundPosterUrl: String? = null,
+ var backgroundPosterUrl : String? = null,
/** In unixtime */
var startDate: Long? = null,
@@ -99,61 +76,4 @@ interface SyncAPI : OAuth2API {
var prevSeason: SyncSearchResult? = null,
var actors: List? = null,
)
-
-
- data class Page(
- val title: UiText, var items: List
- ) {
- fun sort(method: ListSorting?, query: String? = null) {
- items = when (method) {
- ListSorting.Query ->
- if (query != null) {
- items.sortedBy {
- -FuzzySearch.partialRatio(
- query.lowercase(), it.name.lowercase()
- )
- }
- } else items
- ListSorting.RatingHigh -> items.sortedBy { -(it.personalRating ?: 0) }
- ListSorting.RatingLow -> items.sortedBy { (it.personalRating ?: 0) }
- ListSorting.AlphabeticalA -> items.sortedBy { it.name }
- ListSorting.AlphabeticalZ -> items.sortedBy { it.name }.reversed()
- ListSorting.UpdatedNew -> items.sortedBy { it.lastUpdatedUnixTime?.times(-1) }
- ListSorting.UpdatedOld -> items.sortedBy { it.lastUpdatedUnixTime }
- else -> items
- }
- }
- }
-
- data class LibraryMetadata(
- val allLibraryLists: List,
- val supportedListSorting: Set
- )
-
- data class LibraryList(
- val name: UiText,
- val items: List
- )
-
- data class LibraryItem(
- override val name: String,
- override val url: String,
- /**
- * Unique unchanging string used for data storage.
- * This should be the actual id when you change scores and status
- * since score changes from library might get added in the future.
- **/
- val syncId: String,
- val episodesCompleted: Int?,
- val episodesTotal: Int?,
- /** Out of 100 */
- val personalRating: Int?,
- val lastUpdatedUnixTime: Long?,
- override val apiName: String,
- override var type: TvType?,
- override var posterUrl: String?,
- override var posterHeaders: Map?,
- override var quality: SearchQuality?,
- override var id: Int? = null,
- ) : SearchResponse
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncRepo.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncRepo.kt
index 85b877e0..b621e81a 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncRepo.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncRepo.kt
@@ -11,38 +11,26 @@ class SyncRepo(private val repo: SyncAPI) {
val icon = repo.icon
val mainUrl = repo.mainUrl
val requiresLogin = repo.requiresLogin
- val syncIdName = repo.syncIdName
- var requireLibraryRefresh: Boolean
- get() = repo.requireLibraryRefresh
- set(value) {
- repo.requireLibraryRefresh = value
- }
suspend fun score(id: String, status: SyncAPI.SyncStatus): Resource {
return safeApiCall { repo.score(id, status) }
}
- suspend fun getStatus(id: String): Resource {
+ suspend fun getStatus(id : String) : Resource {
return safeApiCall { repo.getStatus(id) ?: throw ErrorLoadingException("No data") }
}
- suspend fun getResult(id: String): Resource {
+ suspend fun getResult(id : String) : Resource {
return safeApiCall { repo.getResult(id) ?: throw ErrorLoadingException("No data") }
}
- suspend fun search(query: String): Resource> {
+ suspend fun search(query : String) : Resource> {
return safeApiCall { repo.search(query) ?: throw ErrorLoadingException() }
}
- suspend fun getPersonalLibrary(): Resource {
- return safeApiCall { repo.getPersonalLibrary() ?: throw ErrorLoadingException() }
- }
-
- fun hasAccount(): Boolean {
+ fun hasAccount() : Boolean {
return normalSafeApiCall { repo.loginInfo() != null } ?: false
}
- fun getIdFromUrl(url: String): String? = normalSafeApiCall {
- repo.getIdFromUrl(url)
- }
+ fun getIdFromUrl(url : String) : String = repo.getIdFromUrl(url)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/Addic7ed.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/Addic7ed.kt
deleted file mode 100644
index 507c5e2a..00000000
--- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/Addic7ed.kt
+++ /dev/null
@@ -1,108 +0,0 @@
-package com.lagradost.cloudstream3.syncproviders.providers
-
-import com.lagradost.cloudstream3.TvType
-import com.lagradost.cloudstream3.app
-import com.lagradost.cloudstream3.subtitles.AbstractSubApi
-import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities
-import com.lagradost.cloudstream3.utils.SubtitleHelper
-
-class Addic7ed : AbstractSubApi {
- override val name = "Addic7ed"
- override val idPrefix = "addic7ed"
- override val requiresLogin = false
- override val icon: Nothing? = null
- override val createAccountUrl: Nothing? = null
-
- override fun loginInfo(): Nothing? = null
-
- override fun logOut() {}
-
- companion object {
- const val host = "https://www.addic7ed.com"
- const val TAG = "ADDIC7ED"
- }
-
- private fun fixUrl(url: String): String {
- return if (url.startsWith("/")) host + url
- else if (!url.startsWith("http")) "$host/$url"
- else url
-
- }
-
- override suspend fun search(query: AbstractSubtitleEntities.SubtitleSearch): List {
- val lang = query.lang
- val queryLang = SubtitleHelper.fromTwoLettersToLanguage(lang.toString())
- val queryText = query.query.trim()
- val epNum = query.epNumber ?: 0
- val seasonNum = query.seasonNumber ?: 0
- val yearNum = query.year ?: 0
-
- fun cleanResources(
- results: MutableList,
- name: String,
- link: String,
- headers: Map,
- isHearingImpaired: Boolean
- ) {
- results.add(
- AbstractSubtitleEntities.SubtitleEntity(
- idPrefix = idPrefix,
- name = name,
- lang = queryLang.toString(),
- data = link,
- source = this.name,
- type = if (seasonNum > 0) TvType.TvSeries else TvType.Movie,
- epNumber = epNum,
- seasonNumber = seasonNum,
- year = yearNum,
- headers = headers,
- isHearingImpaired = isHearingImpaired
- )
- )
- }
-
- val title = queryText.substringBefore("(").trim()
- val url = "$host/search.php?search=${title}&Submit=Search"
- val hostDocument = app.get(url).document
- var searchResult = ""
- if (!hostDocument.select("span:contains($title)").isNullOrEmpty()) searchResult = url
- else if (!hostDocument.select("table.tabel")
- .isNullOrEmpty()
- ) searchResult = hostDocument.select("a:contains($title)").attr("href").toString()
- else {
- val show =
- hostDocument.selectFirst("#sl button")?.attr("onmouseup")?.substringAfter("(")
- ?.substringBefore(",")
- val doc = app.get(
- "$host/ajax_loadShow.php?show=$show&season=$seasonNum&langs=&hd=undefined&hi=undefined",
- referer = "$host/"
- ).document
- doc.select("#season tr:contains($queryLang)").mapNotNull { node ->
- if (node.selectFirst("td")?.text()
- ?.toIntOrNull() == seasonNum && node.select("td:eq(1)")
- .text()
- .toIntOrNull() == epNum
- ) searchResult = fixUrl(node.select("a").attr("href"))
- }
- }
- val results = mutableListOf()
- val document = app.get(
- url = fixUrl(searchResult),
- ).document
-
- document.select(".tabel95 .tabel95 tr:contains($queryLang)").mapNotNull { node ->
- val name = if (seasonNum > 0) "${document.select(".titulo").text().replace("Subtitle","").trim()}${
- node.parent()!!.select(".NewsTitle").text().substringAfter("Version").substringBefore(", Duration")
- }" else "${document.select(".titulo").text().replace("Subtitle","").trim()}${node.parent()!!.select(".NewsTitle").text().substringAfter("Version").substringBefore(", Duration")}"
- val link = fixUrl(node.select("a.buttonDownload").attr("href"))
- val isHearingImpaired =
- !node.parent()!!.select("tr:last-child [title=\"Hearing Impaired\"]").isNullOrEmpty()
- cleanResources(results, name, link, mapOf("referer" to "$host/"), isHearingImpaired)
- }
- return results
- }
-
- override suspend fun load(data: AbstractSubtitleEntities.SubtitleEntity): String {
- return data.data
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt
index 0010ce25..3140abbc 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt
@@ -1,20 +1,19 @@
package com.lagradost.cloudstream3.syncproviders.providers
-import androidx.annotation.StringRes
import androidx.fragment.app.FragmentActivity
import com.fasterxml.jackson.annotation.JsonProperty
+import com.fasterxml.jackson.databind.DeserializationFeature
+import com.fasterxml.jackson.databind.json.JsonMapper
+import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
+import com.lagradost.cloudstream3.AcraApplication.Companion.getKeys
import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.mvvm.logError
-import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
import com.lagradost.cloudstream3.syncproviders.AccountManager
import com.lagradost.cloudstream3.syncproviders.AuthAPI
import com.lagradost.cloudstream3.syncproviders.SyncAPI
-import com.lagradost.cloudstream3.syncproviders.SyncIdName
-import com.lagradost.cloudstream3.ui.library.ListSorting
-import com.lagradost.cloudstream3.ui.result.txt
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
import com.lagradost.cloudstream3.utils.AppUtils.splitQuery
import com.lagradost.cloudstream3.utils.AppUtils.toJson
@@ -22,7 +21,6 @@ import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.DataStore.toKotlinObject
import java.net.URL
-import java.net.URLEncoder
import java.util.*
class AniListApi(index: Int) : AccountManager(index), SyncAPI {
@@ -30,12 +28,10 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
override val key = "6871"
override val redirectUrl = "anilistlogin"
override val idPrefix = "anilist"
- override var requireLibraryRefresh = true
override var mainUrl = "https://anilist.co"
override val icon = R.drawable.ic_anilist_icon
override val requiresLogin = false
override val createAccountUrl = "$mainUrl/signup"
- override val syncIdName = SyncIdName.Anilist
override fun loginInfo(): AuthAPI.LoginInfo? {
// context.getUser(true)?.
@@ -50,7 +46,6 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
}
override fun logOut() {
- requireLibraryRefresh = true
removeAccountKeys()
}
@@ -70,8 +65,8 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
switchToNewAccount()
setKey(accountId, ANILIST_UNIXTIME_KEY, endTime)
setKey(accountId, ANILIST_TOKEN_KEY, token)
+ setKey(ANILIST_SHOULD_UPDATE_LIST, true)
val user = getUser()
- requireLibraryRefresh = true
return user != null
}
@@ -146,8 +141,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
this.name,
recMedia.id?.toString() ?: return@mapNotNull null,
getUrlFromId(recMedia.id),
- recMedia.coverImage?.extraLarge ?: recMedia.coverImage?.large
- ?: recMedia.coverImage?.medium
+ recMedia.coverImage?.large ?: recMedia.coverImage?.medium
)
},
trailers = when (season.trailer?.site?.lowercase()?.trim()) {
@@ -177,9 +171,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
fromIntToAnimeStatus(status.status),
status.score,
status.watchedEpisodes
- ).also {
- requireLibraryRefresh = requireLibraryRefresh || it
- }
+ )
}
companion object {
@@ -190,6 +182,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
const val ANILIST_TOKEN_KEY: String = "anilist_token" // anilist token for api
const val ANILIST_USER_KEY: String = "anilist_user" // user data like profile
const val ANILIST_CACHED_LIST: String = "anilist_cached_list"
+ const val ANILIST_SHOULD_UPDATE_LIST: String = "anilist_should_update_list"
private fun fixName(name: String): String {
return name.lowercase(Locale.ROOT).replace(" ", "")
@@ -227,7 +220,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
romaji
}
idMal
- coverImage { medium large extraLarge }
+ coverImage { medium large }
averageScore
}
}
@@ -240,7 +233,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
format
id
idMal
- coverImage { medium large extraLarge }
+ coverImage { medium large }
averageScore
title {
english
@@ -300,13 +293,15 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
val shows = searchShows(name.replace(blackListRegex, ""))
shows?.data?.Page?.media?.find {
- (malId ?: "NONE") == it.idMal.toString()
+ malId ?: "NONE" == it.idMal.toString()
}?.let { return it }
val filtered =
shows?.data?.Page?.media?.filter {
- (((it.startDate.year ?: year.toString()) == year.toString()
- || year == null))
+ (
+ it.startDate.year ?: year.toString() == year.toString()
+ || year == null
+ )
}
filtered?.forEach {
it.title.romaji?.let { romaji ->
@@ -318,14 +313,14 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
}
// Changing names of these will show up in UI
- enum class AniListStatusType(var value: Int, @StringRes val stringRes: Int) {
- Watching(0, R.string.type_watching),
- Completed(1, R.string.type_completed),
- Paused(2, R.string.type_on_hold),
- Dropped(3, R.string.type_dropped),
- Planning(4, R.string.type_plan_to_watch),
- ReWatching(5, R.string.type_re_watching),
- None(-1, R.string.none)
+ enum class AniListStatusType(var value: Int) {
+ Watching(0),
+ Completed(1),
+ Paused(2),
+ Dropped(3),
+ Planning(4),
+ ReWatching(5),
+ None(-1)
}
fun fromIntToAnimeStatus(inp: Int): AniListStatusType {//= AniListStatusType.values().first { it.value == inp }
@@ -341,7 +336,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
}
}
- fun convertAniListStringToStatus(string: String): AniListStatusType {
+ fun convertAnilistStringToStatus(string: String): AniListStatusType {
return fromIntToAnimeStatus(aniListStatusString.indexOf(string))
}
@@ -527,27 +522,19 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
}
private suspend fun postApi(q: String, cache: Boolean = false): String? {
- return suspendSafeApiCall {
- if (!checkToken()) {
- app.post(
- "https://graphql.anilist.co/",
- headers = mapOf(
- "Authorization" to "Bearer " + (getAuth()
- ?: return@suspendSafeApiCall null),
- if (cache) "Cache-Control" to "max-stale=$maxStale" else "Cache-Control" to "no-cache"
- ),
- cacheTime = 0,
- data = mapOf(
- "query" to URLEncoder.encode(
- q,
- "UTF-8"
- )
- ), //(if (vars == null) mapOf("query" to q) else mapOf("query" to q, "variables" to vars))
- timeout = 5 // REASONABLE TIMEOUT
- ).text.replace("\\/", "/")
- } else {
- null
- }
+ return if (!checkToken()) {
+ app.post(
+ "https://graphql.anilist.co/",
+ headers = mapOf(
+ "Authorization" to "Bearer " + (getAuth() ?: return null),
+ if (cache) "Cache-Control" to "max-stale=$maxStale" else "Cache-Control" to "no-cache"
+ ),
+ cacheTime = 0,
+ data = mapOf("query" to q),//(if (vars == null) mapOf("query" to q) else mapOf("query" to q, "variables" to vars))
+ timeout = 5 // REASONABLE TIMEOUT
+ ).text.replace("\\/", "/")
+ } else {
+ null
}
}
@@ -582,8 +569,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
data class CoverImage(
@JsonProperty("medium") val medium: String?,
- @JsonProperty("large") val large: String?,
- @JsonProperty("extraLarge") val extraLarge: String?
+ @JsonProperty("large") val large: String?
)
data class Media(
@@ -610,29 +596,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
@JsonProperty("score") val score: Int,
@JsonProperty("private") val private: Boolean,
@JsonProperty("media") val media: Media
- ) {
- fun toLibraryItem(): SyncAPI.LibraryItem {
- return SyncAPI.LibraryItem(
- // English title first
- this.media.title.english ?: this.media.title.romaji
- ?: this.media.synonyms.firstOrNull()
- ?: "",
- "https://anilist.co/anime/${this.media.id}/",
- this.media.id.toString(),
- this.progress,
- this.media.episodes,
- this.score,
- this.updatedAt.toLong(),
- "AniList",
- TvType.Anime,
- this.media.coverImage.extraLarge ?: this.media.coverImage.large
- ?: this.media.coverImage.medium,
- null,
- null,
- null
- )
- }
- }
+ )
data class Lists(
@JsonProperty("status") val status: String?,
@@ -647,59 +611,40 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
@JsonProperty("MediaListCollection") val MediaListCollection: MediaListCollection
)
- private fun getAniListListCached(): Array? {
+ fun getAnilistListCached(): Array? {
return getKey(ANILIST_CACHED_LIST) as? Array
}
- private suspend fun getAniListAnimeListSmart(): Array? {
+ suspend fun getAnilistAnimeListSmart(): Array? {
if (getAuth() == null) return null
if (checkToken()) return null
- return if (requireLibraryRefresh) {
- val list = getFullAniListList()?.data?.MediaListCollection?.lists?.toTypedArray()
+ return if (getKey(ANILIST_SHOULD_UPDATE_LIST, true) == true) {
+ val list = getFullAnilistList()?.data?.MediaListCollection?.lists?.toTypedArray()
if (list != null) {
setKey(ANILIST_CACHED_LIST, list)
+ setKey(ANILIST_SHOULD_UPDATE_LIST, false)
}
list
} else {
- getAniListListCached()
+ getAnilistListCached()
}
}
- override suspend fun getPersonalLibrary(): SyncAPI.LibraryMetadata {
- val list = getAniListAnimeListSmart()?.groupBy {
- convertAniListStringToStatus(it.status ?: "").stringRes
- }?.mapValues { group ->
- group.value.map { it.entries.map { entry -> entry.toLibraryItem() } }.flatten()
- } ?: emptyMap()
-
- // To fill empty lists when AniList does not return them
- val baseMap =
- AniListStatusType.values().filter { it.value >= 0 }.associate {
- it.stringRes to emptyList()
- }
-
- return SyncAPI.LibraryMetadata(
- (baseMap + list).map { SyncAPI.LibraryList(txt(it.key), it.value) },
- setOf(
- ListSorting.AlphabeticalA,
- ListSorting.AlphabeticalZ,
- ListSorting.UpdatedNew,
- ListSorting.UpdatedOld,
- ListSorting.RatingHigh,
- ListSorting.RatingLow,
- )
- )
- }
-
- private suspend fun getFullAniListList(): FullAnilistList? {
+ private suspend fun getFullAnilistList(): FullAnilistList? {
+ var userID: Int? = null
/** WARNING ASSUMES ONE USER! **/
+ getKeys(ANILIST_USER_KEY)?.forEach { key ->
+ getKey(key, null)?.let {
+ userID = it.id
+ }
+ }
- val userID = getKey(accountId, ANILIST_USER_KEY)?.id ?: return null
+ val fixedUserID = userID ?: return null
val mediaType = "ANIME"
val query = """
- query (${'$'}userID: Int = $userID, ${'$'}MEDIA: MediaType = $mediaType) {
+ query (${'$'}userID: Int = $fixedUserID, ${'$'}MEDIA: MediaType = $mediaType) {
MediaListCollection (userId: ${'$'}userID, type: ${'$'}MEDIA) {
lists {
status
@@ -710,7 +655,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
startedAt { year month day }
updatedAt
progress
- score (format: POINT_100)
+ score
private
media
{
@@ -726,7 +671,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
english
romaji
}
- coverImage { extraLarge large medium }
+ coverImage { medium }
synonyms
nextAiringEpisode {
timeUntilAiring
@@ -759,11 +704,6 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
return data != ""
}
- /** Used to query a saved MediaItem on the list to get the id for removal */
- data class MediaListItemRoot(@JsonProperty("data") val data: MediaListItem? = null)
- data class MediaListItem(@JsonProperty("MediaList") val MediaList: MediaListId? = null)
- data class MediaListId(@JsonProperty("id") val id: Long? = null)
-
private suspend fun postDataAboutId(
id: Int,
type: AniListStatusType,
@@ -771,43 +711,19 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
progress: Int?
): Boolean {
val q =
- // Delete item if status type is None
- if (type == AniListStatusType.None) {
- val userID = getKey(accountId, ANILIST_USER_KEY)?.id ?: return false
- // Get list ID for deletion
- val idQuery = """
- query MediaList(${'$'}userId: Int = $userID, ${'$'}mediaId: Int = $id) {
- MediaList(userId: ${'$'}userId, mediaId: ${'$'}mediaId) {
- id
- }
- }
- """
- val response = postApi(idQuery)
- val listId =
- tryParseJson(response)?.data?.MediaList?.id ?: return false
- """
- mutation(${'$'}id: Int = $listId) {
- DeleteMediaListEntry(id: ${'$'}id) {
- deleted
- }
- }
- """
- } else {
- """mutation (${'$'}id: Int = $id, ${'$'}status: MediaListStatus = ${
- aniListStatusString[maxOf(
- 0,
- type.value
- )]
- }, ${if (score != null) "${'$'}scoreRaw: Int = ${score * 10}" else ""} , ${if (progress != null) "${'$'}progress: Int = $progress" else ""}) {
- SaveMediaListEntry (mediaId: ${'$'}id, status: ${'$'}status, scoreRaw: ${'$'}scoreRaw, progress: ${'$'}progress) {
- id
- status
- progress
- score
- }
+ """mutation (${'$'}id: Int = $id, ${'$'}status: MediaListStatus = ${
+ aniListStatusString[maxOf(
+ 0,
+ type.value
+ )]
+ }, ${if (score != null) "${'$'}scoreRaw: Int = ${score * 10}" else ""} , ${if (progress != null) "${'$'}progress: Int = $progress" else ""}) {
+ SaveMediaListEntry (mediaId: ${'$'}id, status: ${'$'}status, scoreRaw: ${'$'}scoreRaw, progress: ${'$'}progress) {
+ id
+ status
+ progress
+ score
+ }
}"""
- }
-
val data = postApi(q)
return data != ""
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/IndexSubtitleApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/IndexSubtitleApi.kt
index 668d10bd..2fc97477 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/IndexSubtitleApi.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/IndexSubtitleApi.kt
@@ -4,6 +4,7 @@ import android.util.Log
import com.lagradost.cloudstream3.TvType
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.imdbUrlToIdNullable
+import com.lagradost.cloudstream3.network.CloudflareKiller
import com.lagradost.cloudstream3.subtitles.AbstractSubApi
import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities
import com.lagradost.cloudstream3.utils.SubtitleHelper
@@ -21,7 +22,7 @@ class IndexSubtitleApi : AbstractSubApi {
companion object {
- const val host = "https://indexsubtitle.com"
+ const val host = "https://subscene.cyou"
const val TAG = "INDEXSUBS"
}
@@ -241,7 +242,7 @@ class IndexSubtitleApi : AbstractSubApi {
document.selectFirst("div.my-3.p-3 div.media a")!!.attr("href")
)
} else {
- document.select("div.my-3.p-3 div.media").firstNotNullOf { block ->
+ document.select("div.my-3.p-3 div.media").mapNotNull { block ->
val name =
block.selectFirst("strong.d-block")?.text()?.trim().toString()
if (seasonNum!! > 0) {
@@ -253,7 +254,7 @@ class IndexSubtitleApi : AbstractSubApi {
} else {
fixUrl(block.selectFirst("a")!!.attr("href"))
}
- }
+ }.first()
}
return link
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt
deleted file mode 100644
index 7dd43fe7..00000000
--- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt
+++ /dev/null
@@ -1,104 +0,0 @@
-package com.lagradost.cloudstream3.syncproviders.providers
-
-import androidx.fragment.app.FragmentActivity
-import com.lagradost.cloudstream3.R
-import com.lagradost.cloudstream3.syncproviders.AuthAPI
-import com.lagradost.cloudstream3.syncproviders.SyncAPI
-import com.lagradost.cloudstream3.syncproviders.SyncIdName
-import com.lagradost.cloudstream3.ui.WatchType
-import com.lagradost.cloudstream3.ui.library.ListSorting
-import com.lagradost.cloudstream3.ui.result.txt
-import com.lagradost.cloudstream3.utils.Coroutines.ioWork
-import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllSubscriptions
-import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllWatchStateIds
-import com.lagradost.cloudstream3.utils.DataStoreHelper.getBookmarkedData
-import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState
-
-class LocalList : SyncAPI {
- override val name = "Local"
- override val icon: Int = R.drawable.ic_baseline_storage_24
- override val requiresLogin = false
- override val createAccountUrl: Nothing? = null
- override val idPrefix = "local"
- override var requireLibraryRefresh = true
-
- override fun loginInfo(): AuthAPI.LoginInfo {
- return AuthAPI.LoginInfo(
- null,
- null,
- 0
- )
- }
-
- override fun logOut() {
-
- }
-
- override val key: String = ""
- override val redirectUrl = ""
- override suspend fun handleRedirect(url: String): Boolean {
- return true
- }
-
- override fun authenticate(activity: FragmentActivity?) {
- }
-
- override val mainUrl = ""
- override val syncIdName = SyncIdName.LocalList
- override suspend fun score(id: String, status: SyncAPI.SyncStatus): Boolean {
- return true
- }
-
- override suspend fun getStatus(id: String): SyncAPI.SyncStatus? {
- return null
- }
-
- override suspend fun getResult(id: String): SyncAPI.SyncResult? {
- return null
- }
-
- override suspend fun search(name: String): List? {
- return null
- }
-
- override suspend fun getPersonalLibrary(): SyncAPI.LibraryMetadata? {
- val watchStatusIds = ioWork {
- getAllWatchStateIds()?.map { id ->
- Pair(id, getResultWatchState(id))
- }
- }?.distinctBy { it.first } ?: return null
-
- val list = ioWork {
- watchStatusIds.groupBy {
- it.second.stringRes
- }.mapValues { group ->
- group.value.mapNotNull {
- getBookmarkedData(it.first)?.toLibraryItem(it.first.toString())
- }
- } + mapOf(R.string.subscription_list_name to getAllSubscriptions().mapNotNull {
- it.toLibraryItem()
- })
- }
-
- val baseMap = WatchType.values().filter { it != WatchType.NONE }.associate {
- // None is not something to display
- it.stringRes to emptyList()
- } + mapOf(R.string.subscription_list_name to emptyList())
-
- return SyncAPI.LibraryMetadata(
- (baseMap + list).map { SyncAPI.LibraryList(txt(it.key), it.value) },
- setOf(
- ListSorting.AlphabeticalA,
- ListSorting.AlphabeticalZ,
-// ListSorting.UpdatedNew,
-// ListSorting.UpdatedOld,
-// ListSorting.RatingHigh,
-// ListSorting.RatingLow,
- )
- )
- }
-
- override fun getIdFromUrl(url: String): String {
- return url
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt
index 5164b606..c08958ce 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt
@@ -1,7 +1,6 @@
package com.lagradost.cloudstream3.syncproviders.providers
import android.util.Base64
-import androidx.annotation.StringRes
import androidx.fragment.app.FragmentActivity
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
@@ -9,15 +8,11 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.ShowStatus
-import com.lagradost.cloudstream3.TvType
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.syncproviders.AccountManager
import com.lagradost.cloudstream3.syncproviders.AuthAPI
import com.lagradost.cloudstream3.syncproviders.SyncAPI
-import com.lagradost.cloudstream3.syncproviders.SyncIdName
-import com.lagradost.cloudstream3.ui.library.ListSorting
-import com.lagradost.cloudstream3.ui.result.txt
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
import com.lagradost.cloudstream3.utils.AppUtils.splitQuery
import com.lagradost.cloudstream3.utils.DataStore.toKotlinObject
@@ -36,15 +31,13 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
override val redirectUrl = "mallogin"
override val idPrefix = "mal"
override var mainUrl = "https://myanimelist.net"
- private val apiUrl = "https://api.myanimelist.net"
+ val apiUrl = "https://api.myanimelist.net"
override val icon = R.drawable.mal_logo
override val requiresLogin = false
- override val syncIdName = SyncIdName.MyAnimeList
- override var requireLibraryRefresh = true
+
override val createAccountUrl = "$mainUrl/register.php"
override fun logOut() {
- requireLibraryRefresh = true
removeAccountKeys()
}
@@ -97,9 +90,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
fromIntToAnimeStatus(status.status),
status.score,
status.watchedEpisodes
- ).also {
- requireLibraryRefresh = requireLibraryRefresh || it
- }
+ )
}
data class MalAnime(
@@ -257,45 +248,10 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
const val MAL_USER_KEY: String = "mal_user" // user data like profile
const val MAL_CACHED_LIST: String = "mal_cached_list"
+ const val MAL_SHOULD_UPDATE_LIST: String = "mal_should_update_list"
const val MAL_UNIXTIME_KEY: String = "mal_unixtime" // When token expires
const val MAL_REFRESH_TOKEN_KEY: String = "mal_refresh_token" // refresh token
const val MAL_TOKEN_KEY: String = "mal_token" // anilist token for api
-
- fun convertToStatus(string: String): MalStatusType {
- return fromIntToAnimeStatus(malStatusAsString.indexOf(string))
- }
-
- enum class MalStatusType(var value: Int, @StringRes val stringRes: Int) {
- Watching(0, R.string.type_watching),
- Completed(1, R.string.type_completed),
- OnHold(2, R.string.type_on_hold),
- Dropped(3, R.string.type_dropped),
- PlanToWatch(4, R.string.type_plan_to_watch),
- None(-1, R.string.type_none)
- }
-
- private fun fromIntToAnimeStatus(inp: Int): MalStatusType {//= AniListStatusType.values().first { it.value == inp }
- return when (inp) {
- -1 -> MalStatusType.None
- 0 -> MalStatusType.Watching
- 1 -> MalStatusType.Completed
- 2 -> MalStatusType.OnHold
- 3 -> MalStatusType.Dropped
- 4 -> MalStatusType.PlanToWatch
- 5 -> MalStatusType.Watching
- else -> MalStatusType.None
- }
- }
-
- private fun parseDateLong(string: String?): Long? {
- return try {
- SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").parse(
- string ?: return null
- )?.time?.div(1000)
- } catch (e: Exception) {
- null
- }
- }
}
override suspend fun handleRedirect(url: String): Boolean {
@@ -319,7 +275,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
switchToNewAccount()
storeToken(res)
val user = getMalUser()
- requireLibraryRefresh = true
+ setKey(MAL_SHOULD_UPDATE_LIST, true)
return user != null
}
}
@@ -352,10 +308,9 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
setKey(accountId, MAL_UNIXTIME_KEY, (token.expires_in + unixTime))
setKey(accountId, MAL_REFRESH_TOKEN_KEY, token.refresh_token)
setKey(accountId, MAL_TOKEN_KEY, token.access_token)
- requireLibraryRefresh = true
}
} catch (e: Exception) {
- logError(e)
+ e.printStackTrace()
}
}
@@ -374,7 +329,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
).text
storeToken(res)
} catch (e: Exception) {
- logError(e)
+ e.printStackTrace()
}
}
@@ -427,24 +382,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
data class Data(
@JsonProperty("node") val node: Node,
@JsonProperty("list_status") val list_status: ListStatus?,
- ) {
- fun toLibraryItem(): SyncAPI.LibraryItem {
- return SyncAPI.LibraryItem(
- this.node.title,
- "https://myanimelist.net/anime/${this.node.id}/",
- this.node.id.toString(),
- this.list_status?.num_episodes_watched,
- this.node.num_episodes,
- this.list_status?.score?.times(10),
- parseDateLong(this.list_status?.updated_at),
- "MAL",
- TvType.Anime,
- this.node.main_picture?.large ?: this.node.main_picture?.medium,
- null,
- null,
- )
- }
- }
+ )
data class Paging(
@JsonProperty("next") val next: String?
@@ -475,43 +413,18 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
return getKey(MAL_CACHED_LIST) as? Array
}
- private suspend fun getMalAnimeListSmart(): Array? {
+ suspend fun getMalAnimeListSmart(): Array? {
if (getAuth() == null) return null
- return if (requireLibraryRefresh) {
+ return if (getKey(MAL_SHOULD_UPDATE_LIST, true) == true) {
val list = getMalAnimeList()
setKey(MAL_CACHED_LIST, list)
+ setKey(MAL_SHOULD_UPDATE_LIST, false)
list
} else {
getMalAnimeListCached()
}
}
- override suspend fun getPersonalLibrary(): SyncAPI.LibraryMetadata {
- val list = getMalAnimeListSmart()?.groupBy {
- convertToStatus(it.list_status?.status ?: "").stringRes
- }?.mapValues { group ->
- group.value.map { it.toLibraryItem() }
- } ?: emptyMap()
-
- // To fill empty lists when MAL does not return them
- val baseMap =
- MalStatusType.values().filter { it.value >= 0 }.associate {
- it.stringRes to emptyList()
- }
-
- return SyncAPI.LibraryMetadata(
- (baseMap + list).map { SyncAPI.LibraryList(txt(it.key), it.value) },
- setOf(
- ListSorting.AlphabeticalA,
- ListSorting.AlphabeticalZ,
- ListSorting.UpdatedNew,
- ListSorting.UpdatedOld,
- ListSorting.RatingHigh,
- ListSorting.RatingLow,
- )
- )
- }
-
private suspend fun getMalAnimeList(): Array {
checkMalToken()
var offset = 0
@@ -527,6 +440,10 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
return fullList.toTypedArray()
}
+ fun convertToStatus(string: String): MalStatusType {
+ return fromIntToAnimeStatus(malStatusAsString.indexOf(string))
+ }
+
private suspend fun getMalAnimeListSlice(offset: Int = 0): MalList? {
val user = "@me"
val auth = getAuth() ?: return null
@@ -640,6 +557,28 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
return user
}
+ enum class MalStatusType(var value: Int) {
+ Watching(0),
+ Completed(1),
+ OnHold(2),
+ Dropped(3),
+ PlanToWatch(4),
+ None(-1)
+ }
+
+ private fun fromIntToAnimeStatus(inp: Int): MalStatusType {//= AniListStatusType.values().first { it.value == inp }
+ return when (inp) {
+ -1 -> MalStatusType.None
+ 0 -> MalStatusType.Watching
+ 1 -> MalStatusType.Completed
+ 2 -> MalStatusType.OnHold
+ 3 -> MalStatusType.Dropped
+ 4 -> MalStatusType.PlanToWatch
+ 5 -> MalStatusType.Watching
+ else -> MalStatusType.None
+ }
+ }
+
private suspend fun setScoreRequest(
id: Int,
status: MalStatusType? = null,
diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/OpenSubtitlesApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/OpenSubtitlesApi.kt
index 3e372c2d..bfa65f62 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/OpenSubtitlesApi.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/OpenSubtitlesApi.kt
@@ -15,8 +15,6 @@ import com.lagradost.cloudstream3.syncproviders.AuthAPI
import com.lagradost.cloudstream3.syncproviders.InAppAuthAPI
import com.lagradost.cloudstream3.syncproviders.InAppAuthAPIManager
import com.lagradost.cloudstream3.utils.AppUtils
-import java.net.URLEncoder
-import java.nio.charset.StandardCharsets
class OpenSubtitlesApi(index: Int) : InAppAuthAPIManager(index), AbstractSubApi {
override val idPrefix = "opensubtitles"
@@ -166,7 +164,7 @@ class OpenSubtitlesApi(index: Int) : InAppAuthAPIManager(index), AbstractSubApi
val fixedLang = fixLanguage(query.lang)
val imdbId = query.imdb ?: 0
- val queryText = query.query
+ val queryText = query.query.replace(" ", "+")
val epNum = query.epNumber ?: 0
val seasonNum = query.seasonNumber ?: 0
val yearNum = query.year ?: 0
@@ -177,7 +175,7 @@ class OpenSubtitlesApi(index: Int) : InAppAuthAPIManager(index), AbstractSubApi
val searchQueryUrl = when (imdbId > 0) {
//Use imdb_id to search if its valid
true -> "$host/subtitles?imdb_id=$imdbId&languages=${fixedLang}$yearQuery$epQuery$seasonQuery"
- false -> "$host/subtitles?query=${queryText}&languages=${fixedLang}$yearQuery$epQuery$seasonQuery"
+ false -> "$host/subtitles?query=$queryText&languages=${fixedLang}$yearQuery$epQuery$seasonQuery"
}
val req = app.get(
@@ -200,13 +198,9 @@ class OpenSubtitlesApi(index: Int) : InAppAuthAPIManager(index), AbstractSubApi
it.data?.forEach { item ->
val attr = item.attributes ?: return@forEach
val featureDetails = attr.featDetails
- //Use filename as name, if its valid
- val filename = attr.files?.firstNotNullOfOrNull { subfile ->
- subfile.fileName
- }
//Use any valid name/title in hierarchy
- val name = filename ?: featureDetails?.movieName ?: featureDetails?.title
- ?: featureDetails?.parentTitle ?: attr.release ?: query.query
+ val name = featureDetails?.movieName ?: featureDetails?.title
+ ?: featureDetails?.parentTitle ?: attr.release ?: ""
val lang = fixLanguageReverse(attr.language)?: ""
val resEpNum = featureDetails?.episodeNumber ?: query.epNumber
val resSeasonNum = featureDetails?.seasonNumber ?: query.seasonNumber
@@ -334,4 +328,4 @@ class OpenSubtitlesApi(index: Int) : InAppAuthAPIManager(index), AbstractSubApi
@JsonProperty("parent_tmdb_id") var parentTmdbId: Int? = null,
@JsonProperty("parent_feature_id") var parentFeatureId: Int? = null
)
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt
index 4ab2e8e2..34cb262c 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt
@@ -1,19 +1,10 @@
package com.lagradost.cloudstream3.ui
import com.lagradost.cloudstream3.*
-import com.lagradost.cloudstream3.APIHolder.unixTime
-import com.lagradost.cloudstream3.APIHolder.unixTimeMS
-import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.safeApiCall
-import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf
import com.lagradost.cloudstream3.utils.ExtractorLink
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.DelicateCoroutinesApi
-import kotlinx.coroutines.GlobalScope.coroutineContext
-import kotlinx.coroutines.async
-import kotlinx.coroutines.delay
class APIRepository(val api: MainAPI) {
companion object {
@@ -33,67 +24,20 @@ class APIRepository(val api: MainAPI) {
fun isInvalidData(data: String): Boolean {
return data.isEmpty() || data == "[]" || data == "about:blank"
}
-
- data class SavedLoadResponse(
- val unixTime: Long,
- val response: LoadResponse,
- val hash: Pair
- )
-
- private val cache = threadSafeListOf()
- private var cacheIndex: Int = 0
- const val cacheSize = 20
- }
-
- private fun afterPluginsLoaded(forceReload: Boolean) {
- if (forceReload) {
- synchronized(cache) {
- cache.clear()
- }
- }
- }
-
- init {
- afterPluginsLoadedEvent += ::afterPluginsLoaded
}
val hasMainPage = api.hasMainPage
- val providerType = api.providerType
val name = api.name
val mainUrl = api.mainUrl
val mainPage = api.mainPage
val hasQuickSearch = api.hasQuickSearch
val vpnStatus = api.vpnStatus
+ val providerType = api.providerType
suspend fun load(url: String): Resource {
return safeApiCall {
if (isInvalidData(url)) throw ErrorLoadingException()
- val fixedUrl = api.fixUrl(url)
- val lookingForHash = Pair(api.name, fixedUrl)
-
- synchronized(cache) {
- for (item in cache) {
- // 10 min save
- if (item.hash == lookingForHash && (unixTime - item.unixTime) < 60 * 10) {
- return@safeApiCall item.response
- }
- }
- }
-
- api.load(fixedUrl)?.also { response ->
- // Remove all blank tags as early as possible
- response.tags = response.tags?.filter { it.isNotBlank() }
- val add = SavedLoadResponse(unixTime, response, lookingForHash)
-
- synchronized(cache) {
- if (cache.size > cacheSize) {
- cache[cacheIndex] = add // rolling cache
- cacheIndex = (cacheIndex + 1) % cacheSize
- } else {
- cache.add(add)
- }
- }
- } ?: throw ErrorLoadingException()
+ api.load(api.fixUrl(url)) ?: throw ErrorLoadingException()
}
}
@@ -118,48 +62,12 @@ class APIRepository(val api: MainAPI) {
}
}
- suspend fun waitForHomeDelay() {
- val delta = api.sequentialMainPageScrollDelay + api.lastHomepageRequest - unixTimeMS
- if (delta < 0) return
- delay(delta)
- }
-
suspend fun getMainPage(page: Int, nameIndex: Int? = null): Resource> {
return safeApiCall {
- api.lastHomepageRequest = unixTimeMS
-
nameIndex?.let { api.mainPage.getOrNull(it) }?.let { data ->
- listOf(
- api.getMainPage(
- page,
- MainPageRequest(data.name, data.data, data.horizontalImages)
- )
- )
- } ?: run {
- if (api.sequentialMainPage) {
- var first = true
- api.mainPage.map { data ->
- if (!first) // dont want to sleep on first request
- delay(api.sequentialMainPageDelay)
- first = false
-
- api.getMainPage(
- page,
- MainPageRequest(data.name, data.data, data.horizontalImages)
- )
- }
- } else {
- with(CoroutineScope(coroutineContext)) {
- api.mainPage.map { data ->
- async {
- api.getMainPage(
- page,
- MainPageRequest(data.name, data.data, data.horizontalImages)
- )
- }
- }.map { it.await() }
- }
- }
+ listOf(api.getMainPage(page, MainPageRequest(data.name, data.data)))
+ } ?: api.mainPage.apmap { data ->
+ api.getMainPage(page, MainPageRequest(data.name, data.data))
}
}
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/AutofitRecyclerView.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/AutofitRecyclerView.kt
index b4c07792..138084fc 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/AutofitRecyclerView.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/AutofitRecyclerView.kt
@@ -7,8 +7,7 @@ import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlin.math.abs
-class GrdLayoutManager(val context: Context, _spanCount: Int) :
- GridLayoutManager(context, _spanCount) {
+class GrdLayoutManager(val context: Context, _spanCount: Int) : GridLayoutManager(context, _spanCount) {
override fun onFocusSearchFailed(
focused: View,
focusDirection: Int,
@@ -35,7 +34,7 @@ class GrdLayoutManager(val context: Context, _spanCount: Int) :
val pos = maxOf(0, getPosition(focused!!) - 2)
parent.scrollToPosition(pos)
super.onRequestChildFocus(parent, state, child, focused)
- } catch (e: Exception) {
+ } catch (e: Exception){
false
}
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/ControllerActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/ControllerActivity.kt
index 46ddce09..ad0b4399 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/ControllerActivity.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/ControllerActivity.kt
@@ -150,7 +150,7 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi
} else {
ChromecastSubtitlesFragment.getCurrentSavedStyle().apply {
val font = TextTrackStyle()
- font.setFontFamily(fontFamily ?: "Google Sans")
+ font.fontFamily = fontFamily ?: "Google Sans"
fontGenericFamily?.let {
font.fontGenericFamily = it
}
@@ -183,9 +183,7 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi
val contentUrl = (remoteMediaClient?.currentItem?.media?.contentUrl
?: remoteMediaClient?.currentItem?.media?.contentId)
- val sortingMethods =
- items.map { "${it.name} ${Qualities.getStringByInt(it.quality)}" }
- .toTypedArray()
+ val sortingMethods = items.map { "${it.name} ${Qualities.getStringByInt(it.quality)}" }.toTypedArray()
val sotringIndex = items.indexOfFirst { it.url == contentUrl }
val arrayAdapter =
@@ -281,7 +279,7 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi
val currentPosition = remoteMediaClient?.approximateStreamPosition
if (currentDuration != null && currentPosition != null)
DataStoreHelper.setViewPos(epData.id, currentPosition, currentDuration)
- } catch (t: Throwable) {
+ } catch (t : Throwable) {
logError(t)
}
@@ -360,8 +358,10 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi
}
}
- override fun onSessionConnected(castSession: CastSession) {
- super.onSessionConnected(castSession)
+ override fun onSessionConnected(castSession: CastSession?) {
+ castSession?.let {
+ super.onSessionConnected(it)
+ }
remoteMediaClient?.queueSetRepeatMode(REPEAT_MODE_REPEAT_OFF, JSONObject())
}
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/EasterEggMonke.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/EasterEggMonke.kt
index 556ebd34..3d28ca94 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/EasterEggMonke.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/EasterEggMonke.kt
@@ -69,7 +69,7 @@ class EasterEggMonke : AppCompatActivity() {
set.duration = (Math.random() * 1500 + 2500).toLong()
set.addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
+ override fun onAnimationEnd(animation: Animator?) {
frame.removeView(newStar)
}
})
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/HeaderViewDecoration.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/HeaderViewDecoration.kt
deleted file mode 100644
index 40c03012..00000000
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/HeaderViewDecoration.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-package com.lagradost.cloudstream3.ui
-
-import android.graphics.Canvas
-import android.graphics.Rect
-import android.view.View
-import androidx.recyclerview.widget.RecyclerView
-
-class HeaderViewDecoration(private val customView: View) : RecyclerView.ItemDecoration() {
- override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
- super.onDraw(c, parent, state)
- customView.layout(parent.left, 0, parent.right, customView.measuredHeight)
- for (i in 0 until parent.childCount) {
- val view = parent.getChildAt(i)
- if (parent.getChildAdapterPosition(view) == 0) {
- c.save()
- val height = customView.measuredHeight
- val top = view.top - height
- c.translate(0f, top.toFloat())
- customView.draw(c)
- c.restore()
- break
- }
- }
- }
-
- override fun getItemOffsets(
- outRect: Rect,
- view: View,
- parent: RecyclerView,
- state: RecyclerView.State
- ) {
- if (parent.getChildAdapterPosition(view) == 0) {
- customView.measure(
- View.MeasureSpec.makeMeasureSpec(parent.measuredWidth, View.MeasureSpec.AT_MOST),
- View.MeasureSpec.makeMeasureSpec(parent.measuredHeight, View.MeasureSpec.AT_MOST)
- )
- outRect.set(0, customView.measuredHeight, 0, 0)
- } else {
- outRect.setEmpty()
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/WatchType.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/WatchType.kt
index eb4eb666..417b4408 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/WatchType.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/WatchType.kt
@@ -5,12 +5,13 @@ import androidx.annotation.StringRes
import com.lagradost.cloudstream3.R
enum class WatchType(val internalId: Int, @StringRes val stringRes: Int, @DrawableRes val iconRes: Int) {
- WATCHING(0, R.string.type_watching, R.drawable.ic_baseline_bookmark_24),
- COMPLETED(1, R.string.type_completed, R.drawable.ic_baseline_bookmark_24),
- ONHOLD(2, R.string.type_on_hold, R.drawable.ic_baseline_bookmark_24),
- DROPPED(3, R.string.type_dropped, R.drawable.ic_baseline_bookmark_24),
- PLANTOWATCH(4, R.string.type_plan_to_watch, R.drawable.ic_baseline_bookmark_24),
- NONE(5, R.string.type_none, R.drawable.ic_baseline_add_24);
+ // FIX ICONS
+ WATCHING(0, R.string.type_watching, R.drawable.ic_baseline_remove_red_eye_24),
+ COMPLETED(1, R.string.type_completed, R.drawable.ic_baseline_check_24),
+ ONHOLD(2, R.string.type_on_hold, R.drawable.ic_baseline_pause_24),
+ DROPPED(3, R.string.type_dropped, R.drawable.ic_baseline_close_24),
+ PLANTOWATCH(4, R.string.type_plan_to_watch, R.drawable.ic_baseline_close_24),
+ NONE(5, R.string.type_none, R.drawable.ic_baseline_remove_red_eye_24);
companion object {
fun fromInternalId(id: Int?) = values().find { value -> value.internalId == id } ?: NONE
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadButtonSetup.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadButtonSetup.kt
index add36f1a..0069be3a 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadButtonSetup.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadButtonSetup.kt
@@ -11,7 +11,6 @@ import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.player.DownloadFileGenerator
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
import com.lagradost.cloudstream3.utils.AppUtils.getNameFull
-import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus
import com.lagradost.cloudstream3.utils.DOWNLOAD_HEADER_CACHE
import com.lagradost.cloudstream3.utils.ExtractorUri
import com.lagradost.cloudstream3.utils.UIHelper.navigate
@@ -50,7 +49,7 @@ object DownloadButtonSetup {
)
.setPositiveButton(R.string.delete, dialogClickListener)
.setNegativeButton(R.string.cancel, dialogClickListener)
- .show().setDefaultFocus()
+ .show()
} catch (e: Exception) {
logError(e)
// ye you somehow fucked up formatting did you?
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt
index e80a8fa5..b2286c99 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt
@@ -24,6 +24,7 @@ import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
import com.lagradost.cloudstream3.ui.player.LinkGenerator
+import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.utils.AppUtils.loadResult
import com.lagradost.cloudstream3.utils.Coroutines.main
import com.lagradost.cloudstream3.utils.DOWNLOAD_EPISODE_CACHE
@@ -39,8 +40,6 @@ import kotlinx.android.synthetic.main.stream_input.*
import android.text.format.Formatter.formatShortFileSize
import androidx.core.widget.doOnTextChanged
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
-import com.lagradost.cloudstream3.ui.player.BasicLink
-import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
import java.net.URI
@@ -179,9 +178,7 @@ class DownloadFragment : Fragment() {
download_list?.adapter = adapter
download_list?.layoutManager = GridLayoutManager(context, 1)
-
- // Should be visible in emulator layout
- download_stream_button?.isGone = isTrueTvSettings()
+ download_stream_button?.isGone = isTvSettings()
download_stream_button?.setOnClickListener {
val dialog =
Dialog(it.context ?: return@setOnClickListener, R.style.AlertDialogCustom)
@@ -225,7 +222,7 @@ class DownloadFragment : Fragment() {
R.id.global_to_navigation_player,
GeneratorPlayer.newInstance(
LinkGenerator(
- listOf(BasicLink(url)),
+ listOf(url),
extract = true,
referer = referer,
isM3u8 = dialog.hls_switch?.isChecked
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt
index 5cf6fc8e..520b6b99 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt
@@ -7,84 +7,100 @@ import android.content.DialogInterface
import android.content.Intent
import android.content.res.Configuration
import android.net.Uri
-import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
import androidx.appcompat.app.AlertDialog
+import androidx.appcompat.widget.SearchView
import androidx.core.view.isGone
import androidx.core.view.isVisible
+import androidx.core.widget.NestedScrollView
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.*
import androidx.preference.PreferenceManager
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.LinearSnapHelper
import androidx.recyclerview.widget.RecyclerView
-import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.button.MaterialButton
-import com.google.android.material.chip.Chip
-import com.google.android.material.chip.ChipGroup
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder.apis
import com.lagradost.cloudstream3.APIHolder.filterProviderByPreferredMedia
+import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
-import com.lagradost.cloudstream3.MainActivity.Companion.bookmarksUpdatedEvent
import com.lagradost.cloudstream3.MainActivity.Companion.mainPluginsLoadedEvent
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.observe
+import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.OAuth2Apis
import com.lagradost.cloudstream3.ui.APIRepository.Companion.noneApi
import com.lagradost.cloudstream3.ui.APIRepository.Companion.randomApi
import com.lagradost.cloudstream3.ui.AutofitRecyclerView
import com.lagradost.cloudstream3.ui.WatchType
import com.lagradost.cloudstream3.ui.quicksearch.QuickSearchFragment
+import com.lagradost.cloudstream3.ui.result.START_ACTION_RESUME_LATEST
+import com.lagradost.cloudstream3.ui.result.setLinearListLayout
import com.lagradost.cloudstream3.ui.search.*
import com.lagradost.cloudstream3.ui.search.SearchHelper.handleSearchClickCallback
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
-import com.lagradost.cloudstream3.utils.AppUtils.addProgramsToContinueWatching
import com.lagradost.cloudstream3.utils.AppUtils.isRecyclerScrollable
-import com.lagradost.cloudstream3.utils.AppUtils.loadResult
import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
-import com.lagradost.cloudstream3.utils.AppUtils.ownHide
-import com.lagradost.cloudstream3.utils.AppUtils.ownShow
-import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus
+import com.lagradost.cloudstream3.utils.AppUtils.setMaxViewPoolSize
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.DataStore.getKey
import com.lagradost.cloudstream3.utils.DataStore.setKey
import com.lagradost.cloudstream3.utils.DataStoreHelper
+import com.lagradost.cloudstream3.utils.DataStoreHelper.deleteAllBookmarkedData
+import com.lagradost.cloudstream3.utils.DataStoreHelper.deleteAllResumeStateIds
+import com.lagradost.cloudstream3.utils.DataStoreHelper.removeLastWatched
+import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultWatchState
import com.lagradost.cloudstream3.utils.Event
+import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showOptionSelectStringRes
import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
+import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbarView
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes
+import com.lagradost.cloudstream3.utils.UIHelper.setImage
+import com.lagradost.cloudstream3.utils.UIHelper.setImageBlur
import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API
-import kotlinx.android.synthetic.main.activity_main_tv.*
+import com.lagradost.cloudstream3.widget.CenterZoomLayoutManager
import kotlinx.android.synthetic.main.fragment_home.*
import kotlinx.android.synthetic.main.fragment_home.home_api_fab
+import kotlinx.android.synthetic.main.fragment_home.home_bookmarked_child_recyclerview
+import kotlinx.android.synthetic.main.fragment_home.home_bookmarked_holder
import kotlinx.android.synthetic.main.fragment_home.home_change_api_loading
+import kotlinx.android.synthetic.main.fragment_home.home_loaded
import kotlinx.android.synthetic.main.fragment_home.home_loading
import kotlinx.android.synthetic.main.fragment_home.home_loading_error
import kotlinx.android.synthetic.main.fragment_home.home_loading_shimmer
import kotlinx.android.synthetic.main.fragment_home.home_loading_statusbar
+import kotlinx.android.synthetic.main.fragment_home.home_main_poster_recyclerview
import kotlinx.android.synthetic.main.fragment_home.home_master_recycler
+import kotlinx.android.synthetic.main.fragment_home.home_plan_to_watch_btt
+import kotlinx.android.synthetic.main.fragment_home.home_provider_meta_info
+import kotlinx.android.synthetic.main.fragment_home.home_provider_name
import kotlinx.android.synthetic.main.fragment_home.home_reload_connection_open_in_browser
import kotlinx.android.synthetic.main.fragment_home.home_reload_connectionerror
+import kotlinx.android.synthetic.main.fragment_home.home_statusbar
+import kotlinx.android.synthetic.main.fragment_home.home_type_completed_btt
+import kotlinx.android.synthetic.main.fragment_home.home_type_dropped_btt
+import kotlinx.android.synthetic.main.fragment_home.home_type_on_hold_btt
+import kotlinx.android.synthetic.main.fragment_home.home_type_watching_btt
+import kotlinx.android.synthetic.main.fragment_home.home_watch_child_recyclerview
+import kotlinx.android.synthetic.main.fragment_home.home_watch_holder
+import kotlinx.android.synthetic.main.fragment_home.home_watch_parent_item_title
import kotlinx.android.synthetic.main.fragment_home.result_error_text
import kotlinx.android.synthetic.main.fragment_home_tv.*
-import kotlinx.android.synthetic.main.fragment_result.*
-import kotlinx.android.synthetic.main.fragment_search.*
import kotlinx.android.synthetic.main.home_episodes_expanded.*
-import kotlinx.android.synthetic.main.tvtypes_chips.*
-import kotlinx.android.synthetic.main.tvtypes_chips.view.*
import java.util.*
-
const val HOME_BOOKMARK_VALUE_LIST = "home_bookmarked_last_list"
const val HOME_PREF_HOMEPAGE = "home_pref_homepage"
@@ -109,32 +125,26 @@ class HomeFragment : Fragment() {
val errorProfilePic = errorProfilePics.random()
- //fun Activity.loadHomepageList(
- // item: HomePageList,
- // deleteCallback: (() -> Unit)? = null,
- //) {
- // loadHomepageList(
- // expand = HomeViewModel.ExpandableHomepageList(item, 1, false),
- // deleteCallback = deleteCallback,
- // expandCallback = null
- // )
- //}
+ fun Activity.loadHomepageList(
+ item: HomePageList,
+ deleteCallback: (() -> Unit)? = null,
+ ) {
+ loadHomepageList(
+ expand = HomeViewModel.ExpandableHomepageList(item, 1, false),
+ deleteCallback = deleteCallback,
+ expandCallback = null
+ )
+ }
- // returns a BottomSheetDialog that will be hidden with OwnHidden upon hide, and must be saved to be able call ownShow in onCreateView
fun Activity.loadHomepageList(
expand: HomeViewModel.ExpandableHomepageList,
deleteCallback: (() -> Unit)? = null,
- expandCallback: (suspend (String) -> HomeViewModel.ExpandableHomepageList?)? = null,
- dismissCallback : (() -> Unit),
- ): BottomSheetDialog {
+ expandCallback: (suspend (String) -> HomeViewModel.ExpandableHomepageList?)? = null
+ ) {
val context = this
val bottomSheetDialogBuilder = BottomSheetDialog(context)
-
bottomSheetDialogBuilder.setContentView(R.layout.home_episodes_expanded)
val title = bottomSheetDialogBuilder.findViewById(R.id.home_expanded_text)!!
-
- //title.findViewTreeLifecycleOwner().lifecycle.addObserver()
-
val item = expand.list
title.text = item.name
val recycle =
@@ -142,23 +152,6 @@ class HomeFragment : Fragment() {
val titleHolder =
bottomSheetDialogBuilder.findViewById(R.id.home_expanded_drag_down)!!
- // main {
- //(bottomSheetDialogBuilder.ownerActivity as androidx.fragment.app.FragmentActivity?)?.supportFragmentManager?.fragments?.lastOrNull()?.viewLifecycleOwner?.apply {
- // println("GOT LIFE: lifecycle $this")
- // this.lifecycle.addObserver(object : DefaultLifecycleObserver {
- // override fun onResume(owner: LifecycleOwner) {
- // super.onResume(owner)
- // println("onResume!!!!")
- // bottomSheetDialogBuilder?.ownShow()
- // }
-
- // override fun onStop(owner: LifecycleOwner) {
- // super.onStop(owner)
- // bottomSheetDialogBuilder?.ownHide()
- // }
- // })
- //}
- // }
val delete = bottomSheetDialogBuilder.home_expanded_delete
delete.isGone = deleteCallback == null
if (deleteCallback != null) {
@@ -184,7 +177,7 @@ class HomeFragment : Fragment() {
)
.setPositiveButton(R.string.delete, dialogClickListener)
.setNegativeButton(R.string.cancel, dialogClickListener)
- .show().setDefaultFocus()
+ .show()
} catch (e: Exception) {
logError(e)
// ye you somehow fucked up formatting did you?
@@ -203,8 +196,7 @@ class HomeFragment : Fragment() {
recycle.adapter = SearchAdapter(item.list.toMutableList(), recycle) { callback ->
handleSearchClickCallback(this, callback)
if (callback.action == SEARCH_ACTION_LOAD || callback.action == SEARCH_ACTION_PLAY_FILE) {
- bottomSheetDialogBuilder.ownHide() // we hide here because we want to resume it later
- //bottomSheetDialogBuilder.dismissSafe(this)
+ bottomSheetDialogBuilder.dismissSafe(this)
}
}.apply {
hasNext = expand.hasNext
@@ -245,27 +237,25 @@ class HomeFragment : Fragment() {
configEvent += spanListener
bottomSheetDialogBuilder.setOnDismissListener {
- dismissCallback.invoke()
configEvent -= spanListener
}
//(recycle.adapter as SearchAdapter).notifyDataSetChanged()
bottomSheetDialogBuilder.show()
- return bottomSheetDialogBuilder
}
fun getPairList(
- anime: Chip?,
- cartoons: Chip?,
- tvs: Chip?,
- docs: Chip?,
- movies: Chip?,
- asian: Chip?,
- livestream: Chip?,
- nsfw: Chip?,
- others: Chip?,
- ): List>> {
+ anime: MaterialButton?,
+ cartoons: MaterialButton?,
+ tvs: MaterialButton?,
+ docs: MaterialButton?,
+ movies: MaterialButton?,
+ asian: MaterialButton?,
+ livestream: MaterialButton?,
+ nsfw: MaterialButton?,
+ others: MaterialButton?,
+ ): List>> {
// This list should be same order as home screen to aid navigation
return listOf(
Pair(movies, listOf(TvType.Movie, TvType.Torrent)),
@@ -280,59 +270,6 @@ class HomeFragment : Fragment() {
)
}
- private fun getPairList(header: ChipGroup) = getPairList(
- header.home_select_anime,
- header.home_select_cartoons,
- header.home_select_tv_series,
- header.home_select_documentaries,
- header.home_select_movies,
- header.home_select_asian,
- header.home_select_livestreams,
- header.home_select_nsfw,
- header.home_select_others
- )
-
- fun validateChips(header: ChipGroup?, validTypes: List) {
- if (header == null) return
- val pairList = getPairList(header)
- for ((button, types) in pairList) {
- val isValid = validTypes.any { types.contains(it) }
- button?.isVisible = isValid
- }
- }
-
- fun updateChips(header: ChipGroup?, selectedTypes: List) {
- if (header == null) return
- val pairList = getPairList(header)
- for ((button, types) in pairList) {
- button?.isChecked =
- button?.isVisible == true && selectedTypes.any { types.contains(it) }
- }
- }
-
- fun bindChips(
- header: ChipGroup?,
- selectedTypes: List,
- validTypes: List,
- callback: (List) -> Unit
- ) {
- if (header == null) return
- val pairList = getPairList(header)
- for ((button, types) in pairList) {
- val isValid = validTypes.any { types.contains(it) }
- button?.isVisible = isValid
- button?.isChecked = isValid && selectedTypes.any { types.contains(it) }
- button?.setOnCheckedChangeListener { _, _ ->
- val list = ArrayList()
- for ((sbutton, vvalidTypes) in pairList) {
- if (sbutton?.isChecked == true)
- list.addAll(vvalidTypes)
- }
- callback(list)
- }
- }
- }
-
fun Context.selectHomepage(selectedApiName: String?, callback: (String) -> Unit) {
val validAPIs = filterProviderByPreferredMedia().toMutableList()
@@ -343,13 +280,10 @@ class HomeFragment : Fragment() {
val builder =
BottomSheetDialog(this)
- builder.behavior.state = BottomSheetBehavior.STATE_EXPANDED
builder.setContentView(R.layout.home_select_mainpage)
builder.show()
builder.let { dialog ->
- val isMultiLang = getApiProviderLangSettings().let { set ->
- set.size > 1 || set.contains(AllLanguagesName)
- }
+ val isMultiLang = getApiProviderLangSettings().size > 1
//dialog.window?.setGravity(Gravity.BOTTOM)
var currentApiName = selectedApiName
@@ -360,9 +294,21 @@ class HomeFragment : Fragment() {
?.toMutableList()
?: mutableListOf(TvType.Movie, TvType.TvSeries)
+ val anime = dialog.findViewById(R.id.home_select_anime)
+ val cartoons = dialog.findViewById(R.id.home_select_cartoons)
+ val tvs = dialog.findViewById(R.id.home_select_tv_series)
+ val docs = dialog.findViewById(R.id.home_select_documentaries)
+ val movies = dialog.findViewById(R.id.home_select_movies)
+ val asian = dialog.findViewById(R.id.home_select_asian)
+ val livestream = dialog.findViewById(R.id.home_select_livestreams)
+ val nsfw = dialog.findViewById(R.id.home_select_nsfw)
+ val others = dialog.findViewById(R.id.home_select_others)
val cancelBtt = dialog.findViewById(R.id.cancel_btt)
val applyBtt = dialog.findViewById(R.id.apply_btt)
+ val pairList =
+ getPairList(anime, cartoons, tvs, docs, movies, asian, livestream, nsfw, others)
+
cancelBtt?.setOnClickListener {
dialog.dismissSafe()
}
@@ -407,14 +353,52 @@ class HomeFragment : Fragment() {
arrayAdapter.notifyDataSetChanged()
}
- bindChips(
- dialog.home_select_group,
- preSelectedTypes,
- validAPIs.flatMap { it.supportedTypes }.distinct()
- ) { list ->
- preSelectedTypes.clear()
- preSelectedTypes.addAll(list)
- updateList()
+ /**
+ * Since fire tv is fucked we need to manually define the focus layout.
+ * Since visible buttons are only known in runtime this is required.
+ **/
+ var lastButton: MaterialButton? = null
+
+ for ((button, validTypes) in pairList) {
+ val isValid =
+ validAPIs.any { api -> validTypes.any { api.supportedTypes.contains(it) } }
+ button?.isVisible = isValid
+ if (isValid) {
+
+ // Set focus navigation
+ button?.let { currentButton ->
+ lastButton?.nextFocusRightId = currentButton.id
+ lastButton?.id?.let { currentButton.nextFocusLeftId = it }
+ lastButton = currentButton
+ }
+
+ fun buttonContains(): Boolean {
+ return preSelectedTypes.any { validTypes.contains(it) }
+ }
+
+ button?.isSelected = buttonContains()
+ button?.setOnClickListener {
+ preSelectedTypes.clear()
+ preSelectedTypes.addAll(validTypes)
+ for ((otherButton, _) in pairList) {
+ otherButton?.isSelected = false
+ }
+ button.isSelected = true
+ updateList()
+ }
+
+ button?.setOnLongClickListener {
+ if (!buttonContains()) {
+ button.isSelected = true
+ preSelectedTypes.addAll(validTypes)
+ } else {
+ button.isSelected = false
+ preSelectedTypes.removeAll(validTypes)
+ }
+ updateList()
+ return@setOnLongClickListener true
+ }
+ }
}
updateList()
}
@@ -430,15 +414,14 @@ class HomeFragment : Fragment() {
): View? {
//homeViewModel =
// ViewModelProvider(this).get(HomeViewModel::class.java)
- bottomSheetDialog?.ownShow()
val layout =
if (isTvSettings()) R.layout.fragment_home_tv else R.layout.fragment_home
return inflater.inflate(layout, container, false)
}
- override fun onDestroyView() {
- bottomSheetDialog?.ownHide()
- super.onDestroyView()
+ private fun toggleMainVisibility(visible: Boolean) {
+ home_main_holder?.isVisible = visible
+ home_main_poster_recyclerview?.isVisible = visible
}
private fun fixGrid() {
@@ -463,26 +446,19 @@ class HomeFragment : Fragment() {
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
- //(home_preview_viewpager?.adapter as? HomeScrollAdapter)?.notifyDataSetChanged()
fixGrid()
}
- fun bookmarksUpdated(_data : Boolean) {
- reloadStored()
- }
-
override fun onResume() {
super.onResume()
reloadStored()
- bookmarksUpdatedEvent += ::bookmarksUpdated
- afterPluginsLoadedEvent += ::afterPluginsLoaded
- mainPluginsLoadedEvent += ::afterMainPluginsLoaded
+ afterPluginsLoadedEvent += ::firstLoadHomePage
+ mainPluginsLoadedEvent += ::firstLoadHomePage
}
override fun onStop() {
- bookmarksUpdatedEvent -= ::bookmarksUpdated
- afterPluginsLoadedEvent -= ::afterPluginsLoaded
- mainPluginsLoadedEvent -= ::afterMainPluginsLoaded
+ afterPluginsLoadedEvent -= ::firstLoadHomePage
+ mainPluginsLoadedEvent -= ::firstLoadHomePage
super.onStop()
}
@@ -495,26 +471,34 @@ class HomeFragment : Fragment() {
homeViewModel.loadStoredData(list)
}
- private fun afterMainPluginsLoaded(unused: Boolean = false) {
+ private fun firstLoadHomePage(successful: Boolean = false) {
+ // dirty hack to make it only load once
loadHomePage(false)
}
- private fun afterPluginsLoaded(forceReload: Boolean) {
- loadHomePage(forceReload)
- }
-
- private fun loadHomePage(forceReload: Boolean) {
+ private fun loadHomePage(forceReload: Boolean = true) {
val apiName = context?.getKey(USER_SELECTED_HOMEPAGE_API)
- if (homeViewModel.apiName.value != apiName || apiName == null || forceReload) {
+ if (homeViewModel.apiName.value != apiName || apiName == null) {
//println("Caught home: " + homeViewModel.apiName.value + " at " + apiName)
homeViewModel.loadAndCancel(apiName, forceReload)
}
}
+ /*private fun handleBack(poppedFragment: Boolean) {
+ if (poppedFragment) {
+ reloadStored()
+ }
+ }*/
+
+ private fun focusCallback(card: SearchResponse) {
+ home_focus_text?.text = card.name
+ home_blur_poster?.setImageBlur(card.posterUrl, 50)
+ }
+
private fun homeHandleSearch(callback: SearchClickCallback) {
if (callback.action == SEARCH_ACTION_FOCUSED) {
- //focusCallback(callback.card)
+ focusCallback(callback.card)
} else {
handleSearchClickCallback(activity, callback)
}
@@ -523,13 +507,12 @@ class HomeFragment : Fragment() {
private var currentApiName: String? = null
private var toggleRandomButton = false
- private var bottomSheetDialog: BottomSheetDialog? = null
-
@SuppressLint("SetTextI18n")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
fixGrid()
+ home_change_api?.setOnClickListener(apiChangeClickListener)
home_change_api_loading?.setOnClickListener(apiChangeClickListener)
home_api_fab?.setOnClickListener(apiChangeClickListener)
home_random?.setOnClickListener {
@@ -546,20 +529,94 @@ class HomeFragment : Fragment() {
home_random?.visibility = View.GONE
}
- observe(homeViewModel.preview) { preview ->
- (home_master_recycler?.adapter as? HomeParentItemAdapterPreview?)?.setPreviewData(
- preview
- )
- }
-
observe(homeViewModel.apiName) { apiName ->
currentApiName = apiName
+ // setKey(USER_SELECTED_HOMEPAGE_API, apiName)
home_api_fab?.text = apiName
- (home_master_recycler?.adapter as? HomeParentItemAdapterPreview?)?.setApiName(
- apiName
- )
+ home_provider_name?.text = apiName
+ try {
+ home_search?.queryHint = getString(R.string.search_hint_site).format(apiName)
+ } catch (e: Exception) {
+ logError(e)
+ }
+ home_provider_meta_info?.isVisible = false
+
+ getApiFromNameNull(apiName)?.let { currentApi ->
+ val typeChoices = listOf(
+ Pair(R.string.movies, listOf(TvType.Movie)),
+ Pair(R.string.tv_series, listOf(TvType.TvSeries)),
+ Pair(R.string.documentaries, listOf(TvType.Documentary)),
+ Pair(R.string.cartoons, listOf(TvType.Cartoon)),
+ Pair(R.string.anime, listOf(TvType.Anime, TvType.OVA, TvType.AnimeMovie)),
+ Pair(R.string.torrent, listOf(TvType.Torrent)),
+ Pair(R.string.asian_drama, listOf(TvType.AsianDrama)),
+ ).filter { item -> currentApi.supportedTypes.any { type -> item.second.contains(type) } }
+ home_provider_meta_info?.text =
+ typeChoices.joinToString(separator = ", ") { getString(it.first) }
+ home_provider_meta_info?.isVisible = true
+ }
}
+ home_main_poster_recyclerview?.adapter =
+ HomeChildItemAdapter(
+ mutableListOf(),
+ R.layout.home_result_big_grid,
+ nextFocusUp = home_main_poster_recyclerview.nextFocusUpId,
+ nextFocusDown = home_main_poster_recyclerview.nextFocusDownId
+ ) { callback ->
+ homeHandleSearch(callback)
+ }
+ home_main_poster_recyclerview.setLinearListLayout()
+ observe(homeViewModel.randomItems) { items ->
+ if (items.isNullOrEmpty()) {
+ toggleMainVisibility(false)
+ } else {
+ val tempAdapter = home_main_poster_recyclerview.adapter as HomeChildItemAdapter?
+ // no need to reload if it has the same data
+ if (tempAdapter != null && tempAdapter.cardList == items) {
+ toggleMainVisibility(true)
+ return@observe
+ }
+
+ val randomSize = items.size
+ tempAdapter?.updateList(items)
+ if (!isTvSettings()) {
+ home_main_poster_recyclerview?.post {
+ (home_main_poster_recyclerview?.layoutManager as CenterZoomLayoutManager?)?.let { manager ->
+ manager.updateSize(forceUpdate = true)
+ if (randomSize > 2) {
+ manager.scrollToPosition(randomSize / 2)
+ manager.snap { dx ->
+ home_main_poster_recyclerview?.post {
+ // this is the best I can do, fuck android for not including instant scroll
+ home_main_poster_recyclerview?.smoothScrollBy(dx, 0)
+ }
+ }
+ }
+ }
+ }
+ } else {
+ items.firstOrNull()?.let {
+ focusCallback(it)
+ }
+ }
+ toggleMainVisibility(true)
+ }
+ }
+
+ home_search?.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
+ override fun onQueryTextSubmit(query: String): Boolean {
+ QuickSearchFragment.pushSearch(activity, query, currentApiName?.let { arrayOf(it) })
+
+ return true
+ }
+
+ override fun onQueryTextChange(newText: String): Boolean {
+ //searchViewModel.quickSearch(newText)
+ return true
+ }
+ })
+
observe(homeViewModel.page) { data ->
when (data) {
is Resource.Success -> {
@@ -569,15 +626,15 @@ class HomeFragment : Fragment() {
val mutableListOfResponse = mutableListOf()
listHomepageItems.clear()
- (home_master_recycler?.adapter as? ParentItemAdapter)?.updateList(
+ // println("ITEMCOUNT: ${d.values.size} ${home_master_recycler?.adapter?.itemCount}")
+ (home_master_recycler?.adapter as? ParentItemAdapter?)?.updateList(
d.values.toMutableList(),
home_master_recycler
)
home_loading?.isVisible = false
home_loading_error?.isVisible = false
- home_master_recycler?.isVisible = true
- //home_loaded?.isVisible = true
+ home_loaded?.isVisible = true
if (toggleRandomButton) {
//Flatten list
d.values.forEach { dlist ->
@@ -617,122 +674,354 @@ class HomeFragment : Fragment() {
home_loading?.isVisible = false
home_loading_error?.isVisible = true
- home_master_recycler?.isVisible = false
- //home_loaded?.isVisible = false
+ home_loaded?.isVisible = false
}
is Resource.Loading -> {
- (home_master_recycler?.adapter as? ParentItemAdapter)?.updateList(listOf())
+ (home_master_recycler?.adapter as? ParentItemAdapter?)?.updateList(listOf())
home_loading_shimmer?.startShimmer()
home_loading?.isVisible = true
home_loading_error?.isVisible = false
- home_master_recycler?.isVisible = false
- //home_loaded?.isVisible = false
+ home_loaded?.isVisible = false
}
}
}
+ val toggleList = listOf(
+ Pair(home_type_watching_btt, WatchType.WATCHING),
+ Pair(home_type_completed_btt, WatchType.COMPLETED),
+ Pair(home_type_dropped_btt, WatchType.DROPPED),
+ Pair(home_type_on_hold_btt, WatchType.ONHOLD),
+ Pair(home_plan_to_watch_btt, WatchType.PLANTOWATCH),
+ )
+ for (item in toggleList) {
+ val watch = item.second
+ item.first?.setOnClickListener {
+ homeViewModel.loadStoredData(EnumSet.of(watch))
+ }
+
+ item.first?.setOnLongClickListener { itemView ->
+ val list = EnumSet.noneOf(WatchType::class.java)
+ itemView.context.getKey(HOME_BOOKMARK_VALUE_LIST)
+ ?.map { WatchType.fromInternalId(it) }?.let {
+ list.addAll(it)
+ }
+
+ if (list.contains(watch)) {
+ list.remove(watch)
+ } else {
+ list.add(watch)
+ }
+ homeViewModel.loadStoredData(list)
+ return@setOnLongClickListener true
+ }
+ }
observe(homeViewModel.availableWatchStatusTypes) { availableWatchStatusTypes ->
context?.setKey(
HOME_BOOKMARK_VALUE_LIST,
availableWatchStatusTypes.first.map { it.internalId }.toIntArray()
)
- (home_master_recycler?.adapter as? HomeParentItemAdapterPreview?)?.setAvailableWatchStatusTypes(
- availableWatchStatusTypes
- )
+
+ for (item in toggleList) {
+ val watch = item.second
+ item.first?.apply {
+ isVisible = availableWatchStatusTypes.second.contains(watch)
+ isSelected = availableWatchStatusTypes.first.contains(watch)
+ }
+ }
+
+ /*home_bookmark_select?.setOnClickListener {
+ it.popupMenuNoIcons(availableWatchStatusTypes.second.map { type ->
+ Pair(
+ type.internalId,
+ type.stringRes
+ )
+ }) {
+ homeViewModel.loadStoredData(it.context, WatchType.fromInternalId(this.itemId))
+ }
+ }
+ home_bookmarked_parent_item_title?.text = getString(availableWatchStatusTypes.first.stringRes)*/
}
- observe(homeViewModel.bookmarks) { data ->
- (home_master_recycler?.adapter as? HomeParentItemAdapterPreview?)?.setBookmarkData(
- data
- )
- }
+ observe(homeViewModel.bookmarks) { (isVis, bookmarks) ->
+ home_bookmarked_holder.isVisible = isVis
- observe(homeViewModel.resumeWatching) { resumeWatching ->
- (home_master_recycler?.adapter as? HomeParentItemAdapterPreview?)?.setResumeWatchingData(
- resumeWatching
+ (home_bookmarked_child_recyclerview?.adapter as? HomeChildItemAdapter?)?.updateList(
+ bookmarks
)
- if (isTrueTvSettings()) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- ioSafe {
- activity?.addProgramsToContinueWatching(resumeWatching.mapNotNull { it as? DataStoreHelper.ResumeWatchingResult })
- }
+
+ home_bookmarked_child_more_info?.setOnClickListener {
+ activity?.loadHomepageList(
+ HomePageList(
+ getString(R.string.error_bookmarks_text), //home_bookmarked_parent_item_title?.text?.toString() ?: getString(R.string.error_bookmarks_text),
+ bookmarks
+ )
+ ) {
+ deleteAllBookmarkedData()
+ homeViewModel.loadStoredData(null)
}
}
}
+ observe(homeViewModel.resumeWatching) { resumeWatching ->
+ home_watch_holder?.isVisible = resumeWatching.isNotEmpty()
+ (home_watch_child_recyclerview?.adapter as? HomeChildItemAdapter?)?.updateList(
+ resumeWatching
+ )
- //context?.fixPaddingStatusbarView(home_statusbar)
- //context?.fixPaddingStatusbar(home_padding)
+ //if (context?.isTvSettings() == true) {
+ // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ // context?.addProgramsToContinueWatching(resumeWatching.mapNotNull { it as? DataStoreHelper.ResumeWatchingResult })
+ // }
+ //}
+
+ home_watch_child_more_info?.setOnClickListener {
+ activity?.loadHomepageList(
+ HomePageList(
+ home_watch_parent_item_title?.text?.toString()
+ ?: getString(R.string.continue_watching),
+ resumeWatching
+ )
+ ) {
+ deleteAllResumeStateIds()
+ homeViewModel.loadResumeWatching()
+ }
+ }
+ }
+
+ home_bookmarked_child_recyclerview.adapter = HomeChildItemAdapter(
+ ArrayList(),
+ nextFocusUp = home_bookmarked_child_recyclerview?.nextFocusUpId,
+ nextFocusDown = home_bookmarked_child_recyclerview?.nextFocusDownId
+ ) { callback ->
+ if (callback.action == SEARCH_ACTION_SHOW_METADATA) {
+ activity?.showOptionSelectStringRes(
+ callback.view,
+ callback.card.posterUrl,
+ listOf(
+ R.string.action_open_watching,
+ R.string.action_remove_from_bookmarks,
+ ),
+ listOf(
+ R.string.action_open_play,
+ R.string.action_open_watching,
+ R.string.action_remove_from_bookmarks
+ )
+ ) { (isTv, actionId) ->
+ fun play() {
+ activity.loadSearchResult(callback.card, START_ACTION_RESUME_LATEST)
+ reloadStored()
+ }
+
+ fun remove() {
+ setResultWatchState(callback.card.id, WatchType.NONE.internalId)
+ reloadStored()
+ }
+
+ fun info() {
+ handleSearchClickCallback(
+ activity,
+ SearchClickCallback(
+ SEARCH_ACTION_LOAD,
+ callback.view,
+ -1,
+ callback.card
+ )
+ )
+ reloadStored()
+ }
+
+ if (isTv) {
+ when (actionId) {
+ 0 -> {
+ play()
+ }
+ 1 -> {
+ info()
+ }
+ 2 -> {
+ remove()
+ }
+ }
+ } else {
+ when (actionId) {
+ 0 -> {
+ info()
+ }
+ 1 -> {
+ remove()
+ }
+ }
+ }
+ }
+ } else {
+ homeHandleSearch(callback)
+ }
+ }
+ home_watch_child_recyclerview.setLinearListLayout()
+ home_bookmarked_child_recyclerview.setLinearListLayout()
+
+ home_watch_child_recyclerview?.adapter = HomeChildItemAdapter(
+ ArrayList(),
+ nextFocusUp = home_watch_child_recyclerview?.nextFocusUpId,
+ nextFocusDown = home_watch_child_recyclerview?.nextFocusDownId
+ ) { callback ->
+ if (callback.action == SEARCH_ACTION_SHOW_METADATA) {
+ activity?.showOptionSelectStringRes(
+ callback.view,
+ callback.card.posterUrl,
+ listOf(
+ R.string.action_open_watching,
+ R.string.action_remove_watching
+ ),
+ listOf(
+ R.string.action_open_play,
+ R.string.action_open_watching,
+ R.string.action_remove_watching
+ )
+ ) { (isTv, actionId) ->
+ fun play() {
+ activity.loadSearchResult(callback.card, START_ACTION_RESUME_LATEST)
+ reloadStored()
+ }
+
+ fun remove() {
+ val card = callback.card
+ if (card is DataStoreHelper.ResumeWatchingResult) {
+ removeLastWatched(card.parentId)
+ reloadStored()
+ }
+ }
+
+ fun info() {
+ handleSearchClickCallback(
+ activity,
+ SearchClickCallback(
+ SEARCH_ACTION_LOAD,
+ callback.view,
+ -1,
+ callback.card
+ )
+ )
+ reloadStored()
+ }
+
+ if (isTv) {
+ when (actionId) {
+ 0 -> {
+ play()
+ }
+ 1 -> {
+ info()
+ }
+ 2 -> {
+ remove()
+ }
+ }
+ } else {
+ when (actionId) {
+ 0 -> {
+ info()
+ }
+ 1 -> {
+ remove()
+ }
+ }
+ }
+
+ }
+ } else {
+ homeHandleSearch(callback)
+ }
+ }
+
+ context?.fixPaddingStatusbarView(home_statusbar)
context?.fixPaddingStatusbar(home_loading_statusbar)
- home_master_recycler?.adapter =
- HomeParentItemAdapterPreview(mutableListOf(), { callback ->
+ home_master_recycler.adapter =
+ ParentItemAdapter(mutableListOf(), { callback ->
homeHandleSearch(callback)
}, { item ->
- bottomSheetDialog = activity?.loadHomepageList(item, expandCallback = {
+ activity?.loadHomepageList(item, expandCallback = {
homeViewModel.expandAndReturn(it)
- }, dismissCallback = {
- bottomSheetDialog = null
})
}, { name ->
homeViewModel.expand(name)
- }, { load ->
- activity?.loadResult(load.response.url, load.response.apiName, load.action)
- }, {
- homeViewModel.loadMoreHomeScrollResponses()
- }, {
- apiChangeClickListener.onClick(it)
- }, reloadStored = {
- reloadStored()
- }, loadStoredData = {
- homeViewModel.loadStoredData(it)
- }, { (isQuickSearch, text) ->
- if (!isQuickSearch) {
- QuickSearchFragment.pushSearch(
- activity,
- text,
- currentApiName?.let { arrayOf(it) })
- }
})
+ home_master_recycler.setLinearListLayout()
+ home_master_recycler?.setMaxViewPoolSize(0, Int.MAX_VALUE)
+ home_master_recycler.layoutManager = object : LinearLayoutManager(context) {
+ override fun supportsPredictiveItemAnimations(): Boolean {
+ return false
+ }
+ } // GridLayoutManager(context, 1).also { it.supportsPredictiveItemAnimations() }
+
+ if (!isTvSettings()) {
+ LinearSnapHelper().attachToRecyclerView(home_main_poster_recyclerview) // snap
+ val centerLayoutManager =
+ CenterZoomLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
+ centerLayoutManager.setOnSizeListener { index ->
+ (home_main_poster_recyclerview?.adapter as HomeChildItemAdapter?)?.cardList?.get(
+ index
+ )?.let { random ->
+ home_main_play?.setOnClickListener {
+ activity.loadSearchResult(random, START_ACTION_RESUME_LATEST)
+ }
+ home_main_info?.setOnClickListener {
+ activity.loadSearchResult(random)
+ }
+
+ home_main_text?.text =
+ random.name + if (random is AnimeSearchResponse && !random.dubStatus.isNullOrEmpty()) {
+ random.dubStatus?.joinToString(
+ prefix = " • ",
+ separator = " | "
+ ) { it.name }
+ } else ""
+ }
+ }
+ home_main_poster_recyclerview?.layoutManager = centerLayoutManager // scale
+ }
reloadStored()
- loadHomePage(false)
- home_master_recycler?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
- override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
- if (dy > 0) { //check for scroll down
- home_api_fab?.shrink() // hide
- home_random?.shrink()
- } else if (dy < -5) {
- if (!isTvSettings()) {
- home_api_fab?.extend() // show
- home_random?.extend()
- }
- }
+ loadHomePage()
- super.onScrolled(recyclerView, dx, dy)
+ home_loaded.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { v, _, scrollY, _, oldScrollY ->
+ val dy = scrollY - oldScrollY
+ if (dy > 0) { //check for scroll down
+ home_api_fab?.shrink() // hide
+ home_random?.shrink()
+ } else if (dy < -5) {
+ if (!isTvSettings()) {
+ home_api_fab?.extend() // show
+ home_random?.extend()
+ }
}
})
// nice profile pic on homepage
- //home_profile_picture_holder?.isVisible = false
+ home_profile_picture_holder?.isVisible = false
// just in case
if (isTvSettings()) {
home_api_fab?.isVisible = false
+ home_change_api?.isVisible = true
if (isTrueTvSettings()) {
home_change_api_loading?.isVisible = true
home_change_api_loading?.isFocusable = true
home_change_api_loading?.isFocusableInTouchMode = true
+ home_change_api?.isFocusable = true
+ home_change_api?.isFocusableInTouchMode = true
}
// home_bookmark_select?.isFocusable = true
// home_bookmark_select?.isFocusableInTouchMode = true
} else {
home_api_fab?.isVisible = true
+ home_change_api?.isVisible = false
home_change_api_loading?.isVisible = false
}
- //TODO READD THIS
- /*for (syncApi in OAuth2Apis) {
+
+ for (syncApi in OAuth2Apis) {
val login = syncApi.loginInfo()
val pic = login?.profilePicture
if (home_profile_picture?.setImage(
@@ -743,6 +1032,6 @@ class HomeFragment : Fragment() {
home_profile_picture_holder?.isVisible = true
break
}
- }*/
+ }
}
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt
index 58c6dbe0..23ab81ce 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt
@@ -4,70 +4,34 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
-import android.widget.LinearLayout
import android.widget.TextView
-import androidx.core.content.ContextCompat
-import androidx.core.view.isGone
-import androidx.core.view.isVisible
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListUpdateCallback
import androidx.recyclerview.widget.RecyclerView
-import androidx.transition.ChangeBounds
-import androidx.transition.TransitionManager
-import androidx.viewpager2.widget.ViewPager2
-import com.google.android.material.chip.Chip
-import com.google.android.material.chip.ChipDrawable
-import com.lagradost.cloudstream3.APIHolder.getId
-import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity
import com.lagradost.cloudstream3.HomePageList
-import com.lagradost.cloudstream3.LoadResponse
import com.lagradost.cloudstream3.R
-import com.lagradost.cloudstream3.SearchResponse
-import com.lagradost.cloudstream3.mvvm.Resource
-import com.lagradost.cloudstream3.ui.WatchType
import com.lagradost.cloudstream3.ui.result.LinearListLayout
-import com.lagradost.cloudstream3.ui.result.ResultViewModel2
-import com.lagradost.cloudstream3.ui.result.START_ACTION_RESUME_LATEST
import com.lagradost.cloudstream3.ui.result.setLinearListLayout
-import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
import com.lagradost.cloudstream3.ui.search.SearchFragment.Companion.filterSearchResponse
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.utils.AppUtils.isRecyclerScrollable
-import com.lagradost.cloudstream3.utils.AppUtils.loadResult
-import com.lagradost.cloudstream3.utils.DataStoreHelper
-import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
-import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbarView
-import kotlinx.android.synthetic.main.activity_main_tv.*
-import kotlinx.android.synthetic.main.activity_main_tv.view.*
-import kotlinx.android.synthetic.main.fragment_home.*
-import kotlinx.android.synthetic.main.fragment_home.view.*
-import kotlinx.android.synthetic.main.fragment_home_head_tv.*
-import kotlinx.android.synthetic.main.fragment_home_head_tv.view.*
-import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_preview
-import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_preview_viewpager
import kotlinx.android.synthetic.main.homepage_parent.view.*
-class LoadClickCallback(
- val action: Int = 0,
- val view: View,
- val position: Int,
- val response: LoadResponse
-)
-open class ParentItemAdapter(
+class ParentItemAdapter(
private var items: MutableList,
private val clickCallback: (SearchClickCallback) -> Unit,
private val moreInfoClickCallback: (HomeViewModel.ExpandableHomepageList) -> Unit,
private val expandCallback: ((String) -> Unit)? = null,
) : RecyclerView.Adapter() {
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
+
+ override fun onCreateViewHolder(parent: ViewGroup, i: Int): ParentViewHolder {
+ //println("onCreateViewHolder $i")
+ val layout =
+ if (isTvSettings()) R.layout.homepage_parent_tv else R.layout.homepage_parent
return ParentViewHolder(
- LayoutInflater.from(parent.context).inflate(
- if (isTvSettings()) R.layout.homepage_parent_tv else R.layout.homepage_parent,
- parent,
- false
- ),
+ LayoutInflater.from(parent.context).inflate(layout, parent, false),
clickCallback,
moreInfoClickCallback,
expandCallback
@@ -75,6 +39,8 @@ open class ParentItemAdapter(
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
+ //println("onBindViewHolder $position")
+
when (holder) {
is ParentViewHolder -> {
holder.bind(items[position])
@@ -115,60 +81,47 @@ open class ParentItemAdapter(
items.clear()
items.addAll(new)
- //val mAdapter = this
- val delta = if (this@ParentItemAdapter is HomeParentItemAdapterPreview) {
- headItems
- } else {
- 0
- }
-
+ val mAdapter = this
diffResult.dispatchUpdatesTo(object : ListUpdateCallback {
override fun onInserted(position: Int, count: Int) {
- //notifyItemRangeChanged(position + delta, count)
- notifyItemRangeInserted(position + delta, count)
+ mAdapter.notifyItemRangeInserted(position, count)
}
override fun onRemoved(position: Int, count: Int) {
- notifyItemRangeRemoved(position + delta, count)
+ mAdapter.notifyItemRangeRemoved(position, count)
}
override fun onMoved(fromPosition: Int, toPosition: Int) {
- notifyItemMoved(fromPosition + delta, toPosition + delta)
+ mAdapter.notifyItemMoved(fromPosition, toPosition)
}
- override fun onChanged(_position: Int, count: Int, payload: Any?) {
-
- val position = _position + delta
-
+ override fun onChanged(position: Int, count: Int, payload: Any?) {
// I know kinda messy, what this does is using the update or bind instead of onCreateViewHolder -> bind
recyclerView?.apply {
// this loops every viewHolder in the recycle view and checks the position to see if it is within the update range
val missingUpdates = (position until (position + count)).toMutableSet()
for (i in 0 until itemCount) {
- val child = getChildAt(i) ?: continue
- val viewHolder = getChildViewHolder(child) ?: continue
- if (viewHolder !is ParentViewHolder) continue
-
- val absolutePosition = viewHolder.bindingAdapterPosition
+ val viewHolder = getChildViewHolder(getChildAt(i))
+ val absolutePosition = viewHolder.absoluteAdapterPosition
if (absolutePosition >= position && absolutePosition < position + count) {
- val expand = items.getOrNull(absolutePosition - delta) ?: continue
- missingUpdates -= absolutePosition
- //println("Updating ${viewHolder.title.text} ($absolutePosition $position) -> ${expand.list.name}")
- if (viewHolder.title.text == expand.list.name) {
- viewHolder.update(expand)
- } else {
- viewHolder.bind(expand)
+ val expand = items.getOrNull(absolutePosition) ?: continue
+ if (viewHolder is ParentViewHolder) {
+ missingUpdates -= absolutePosition
+ if (viewHolder.title.text == expand.list.name) {
+ viewHolder.update(expand)
+ } else {
+ viewHolder.bind(expand)
+ }
}
}
}
// just in case some item did not get updated
for (i in missingUpdates) {
- notifyItemChanged(i, payload)
+ mAdapter.notifyItemChanged(i, payload)
}
- } ?: run {
- // in case we don't have a nice
- notifyItemRangeChanged(position, count, payload)
+ } ?: run { // in case we don't have a nice
+ mAdapter.notifyItemRangeChanged(position, count, payload)
}
}
})
@@ -184,8 +137,9 @@ open class ParentItemAdapter(
private val expandCallback: ((String) -> Unit)? = null,
) :
RecyclerView.ViewHolder(itemView) {
- val title: TextView = itemView.home_child_more_info
- private val recyclerView: RecyclerView = itemView.home_child_recyclerview
+ val title: TextView = itemView.home_parent_item_title
+ val recyclerView: RecyclerView = itemView.home_child_recyclerview
+ private val moreInfo: FrameLayout? = itemView.home_child_more_info
fun update(expand: HomeViewModel.ExpandableHomepageList) {
val info = expand.list
@@ -247,10 +201,9 @@ open class ParentItemAdapter(
})
//(recyclerView.adapter as HomeChildItemAdapter).notifyDataSetChanged()
- if (!isTvSettings()) {
- title.setOnClickListener {
- moreInfoClickCallback.invoke(expand)
- }
+
+ moreInfo?.setOnClickListener {
+ moreInfoClickCallback.invoke(expand)
}
}
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt
deleted file mode 100644
index 94a1a526..00000000
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt
+++ /dev/null
@@ -1,658 +0,0 @@
-package com.lagradost.cloudstream3.ui.home
-
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.FrameLayout
-import androidx.appcompat.widget.SearchView
-import androidx.core.content.ContextCompat
-import androidx.core.view.isGone
-import androidx.core.view.isVisible
-import androidx.recyclerview.widget.RecyclerView
-import androidx.viewpager2.widget.ViewPager2
-import com.google.android.material.chip.Chip
-import com.google.android.material.chip.ChipDrawable
-import com.lagradost.cloudstream3.APIHolder.getId
-import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity
-import com.lagradost.cloudstream3.HomePageList
-import com.lagradost.cloudstream3.LoadResponse
-import com.lagradost.cloudstream3.R
-import com.lagradost.cloudstream3.SearchResponse
-import com.lagradost.cloudstream3.mvvm.Resource
-import com.lagradost.cloudstream3.ui.WatchType
-import com.lagradost.cloudstream3.ui.result.ResultViewModel2
-import com.lagradost.cloudstream3.ui.result.START_ACTION_RESUME_LATEST
-import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD
-import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_SHOW_METADATA
-import com.lagradost.cloudstream3.ui.search.SearchClickCallback
-import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
-import com.lagradost.cloudstream3.utils.DataStoreHelper
-import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
-import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showOptionSelectStringRes
-import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
-import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbarView
-import com.lagradost.cloudstream3.utils.UIHelper.setImage
-import kotlinx.android.synthetic.main.activity_main.view.*
-import kotlinx.android.synthetic.main.fragment_home_head.view.*
-import kotlinx.android.synthetic.main.fragment_home_head.view.home_bookmarked_child_recyclerview
-import kotlinx.android.synthetic.main.fragment_home_head.view.home_watch_parent_item_title
-import kotlinx.android.synthetic.main.fragment_home_head_tv.view.*
-import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_bookmarked_holder
-import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_none_padding
-import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_plan_to_watch_btt
-import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_preview
-import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_preview_viewpager
-import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_type_completed_btt
-import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_type_dropped_btt
-import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_type_on_hold_btt
-import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_type_watching_btt
-import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_watch_child_recyclerview
-import kotlinx.android.synthetic.main.fragment_home_head_tv.view.home_watch_holder
-import kotlinx.android.synthetic.main.toast.view.*
-
-class HomeParentItemAdapterPreview(
- items: MutableList,
- val clickCallback: (SearchClickCallback) -> Unit,
- private val moreInfoClickCallback: (HomeViewModel.ExpandableHomepageList) -> Unit,
- expandCallback: ((String) -> Unit)? = null,
- private val loadCallback: (LoadClickCallback) -> Unit,
- private val loadMoreCallback: (() -> Unit),
- private val changeHomePageCallback: ((View) -> Unit),
- private val reloadStored: (() -> Unit),
- private val loadStoredData: ((Set) -> Unit),
- private val searchQueryCallback: ((Pair) -> Unit)
-) : ParentItemAdapter(items, clickCallback, moreInfoClickCallback, expandCallback) {
- private var previewData: Resource>> = Resource.Loading()
- private var resumeWatchingData: List = listOf()
- private var bookmarkData: Pair> =
- false to listOf()
- private var apiName: String = "NONE"
-
- val headItems = 1
-
- private var availableWatchStatusTypes: Pair, Set> =
- setOf() to setOf()
-
- fun setAvailableWatchStatusTypes(data: Pair, Set>) {
- availableWatchStatusTypes = data
- holder?.setAvailableWatchStatusTypes(data)
- }
-
- companion object {
- private const val VIEW_TYPE_HEADER = 2
- private const val VIEW_TYPE_ITEM = 1
- }
-
- fun setResumeWatchingData(resumeWatching: List) {
- resumeWatchingData = resumeWatching
- holder?.updateResume(resumeWatchingData)
- }
-
- fun setPreviewData(preview: Resource>>) {
- previewData = preview
- holder?.updatePreview(preview)
- }
-
- fun setApiName(name: String) {
- apiName = name
- holder?.updateApiName(name)
- }
-
- fun setBookmarkData(data: Pair>) {
- bookmarkData = data
- holder?.updateBookmarks(data)
- }
-
- override fun getItemViewType(position: Int) = when (position) {
- 0 -> VIEW_TYPE_HEADER
- else -> VIEW_TYPE_ITEM
- }
-
- var holder: HeaderViewHolder? = null
-
- override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
- when (holder) {
- is HeaderViewHolder -> {
- holder.updatePreview(previewData)
- holder.updateResume(resumeWatchingData)
- holder.updateBookmarks(bookmarkData)
- holder.setAvailableWatchStatusTypes(availableWatchStatusTypes)
- holder.updateApiName(apiName)
- }
- else -> super.onBindViewHolder(holder, position - 1)
- }
- }
-
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
- println("onCreateViewHolder $viewType")
- return when (viewType) {
- VIEW_TYPE_HEADER -> HeaderViewHolder(
- LayoutInflater.from(parent.context).inflate(
- if (isTvSettings()) R.layout.fragment_home_head_tv else R.layout.fragment_home_head,
- parent,
- false
- ),
- loadCallback,
- loadMoreCallback,
- changeHomePageCallback,
- clickCallback,
- reloadStored,
- loadStoredData,
- searchQueryCallback,
- moreInfoClickCallback
- ).also {
- this.holder = it
- }
- VIEW_TYPE_ITEM -> super.onCreateViewHolder(parent, viewType)
- else -> error("Unhandled viewType=$viewType")
- }
- }
-
- override fun getItemCount(): Int {
- return super.getItemCount() + headItems
- }
-
- override fun getItemId(position: Int): Long {
- if (position == 0) return previewData.hashCode().toLong()
- return super.getItemId(position - headItems)
- }
-
- override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
- when (holder) {
- is HeaderViewHolder -> {
- holder.onViewDetachedFromWindow()
- }
- else -> super.onViewDetachedFromWindow(holder)
- }
- }
-
- override fun onViewAttachedToWindow(holder: RecyclerView.ViewHolder) {
- when (holder) {
- is HeaderViewHolder -> {
- holder.onViewAttachedToWindow()
- }
- else -> super.onViewAttachedToWindow(holder)
- }
- }
-
-
- class HeaderViewHolder
- constructor(
- itemView: View,
- private val clickCallback: ((LoadClickCallback) -> Unit)?,
- private val loadMoreCallback: (() -> Unit),
- private val changeHomePageCallback: ((View) -> Unit),
- private val searchClickCallback: (SearchClickCallback) -> Unit,
- private val reloadStored: () -> Unit,
- private val loadStoredData: ((Set) -> Unit),
- private val searchQueryCallback: ((Pair) -> Unit),
- private val moreInfoClickCallback: (HomeViewModel.ExpandableHomepageList) -> Unit
- ) : RecyclerView.ViewHolder(itemView) {
- private var previewAdapter: HomeScrollAdapter? = null
- private val previewViewpager: ViewPager2? = itemView.home_preview_viewpager
- private val previewHeader: FrameLayout? = itemView.home_preview
- private val previewCallback: ViewPager2.OnPageChangeCallback =
- object : ViewPager2.OnPageChangeCallback() {
- override fun onPageSelected(position: Int) {
- // home_search?.isIconified = true
- //home_search?.isVisible = true
- //home_search?.clearFocus()
-
- previewAdapter?.apply {
- if (position >= itemCount - 1 && hasMoreItems) {
- hasMoreItems = false // dont make two requests
- loadMoreCallback()
- //homeViewModel.loadMoreHomeScrollResponses()
- }
- }
- previewAdapter?.getItem(position)
- ?.apply {
- //itemView.home_preview_title_holder?.let { parent ->
- // TransitionManager.beginDelayedTransition(
- // parent,
- // ChangeBounds()
- // )
- //}
- itemView.home_preview_description?.isGone =
- this.plot.isNullOrBlank()
- itemView.home_preview_description?.text =
- this.plot ?: ""
- itemView.home_preview_text?.text = this.name
- itemView.home_preview_tags?.apply {
- removeAllViews()
- tags?.forEach { tag ->
- val chip = Chip(context)
- val chipDrawable =
- ChipDrawable.createFromAttributes(
- context,
- null,
- 0,
- R.style.ChipFilledSemiTransparent
- )
- chip.setChipDrawable(chipDrawable)
- chip.text = tag
- chip.isChecked = false
- chip.isCheckable = false
- chip.isFocusable = false
- chip.isClickable = false
- addView(chip)
- }
- }
- itemView.home_preview_tags?.isGone =
- tags.isNullOrEmpty()
- itemView.home_preview_image?.setImage(
- posterUrl,
- posterHeaders
- )
- // itemView.home_preview_title?.text = name
-
- itemView.home_preview_play?.setOnClickListener { view ->
- clickCallback?.invoke(
- LoadClickCallback(
- START_ACTION_RESUME_LATEST,
- view,
- position,
- this
- )
- )
- }
- itemView.home_preview_info?.setOnClickListener { view ->
- clickCallback?.invoke(
- LoadClickCallback(0, view, position, this)
- )
- }
-
- itemView.home_preview_play_btt?.setOnClickListener { view ->
- clickCallback?.invoke(
- LoadClickCallback(
- START_ACTION_RESUME_LATEST,
- view,
- position,
- this
- )
- )
- }
-
- // This makes the hidden next buttons only available when on the info button
- // Otherwise you might be able to go to the next item without being at the info button
- itemView.home_preview_info_btt?.setOnFocusChangeListener { _, hasFocus ->
- itemView.home_preview_hidden_next_focus?.isFocusable = hasFocus
- }
- itemView.home_preview_play_btt?.setOnFocusChangeListener { _, hasFocus ->
- itemView.home_preview_hidden_prev_focus?.isFocusable = hasFocus
- }
-
-
- itemView.home_preview_info_btt?.setOnClickListener { view ->
- clickCallback?.invoke(
- LoadClickCallback(0, view, position, this)
- )
- }
-
- itemView.home_preview_hidden_next_focus?.setOnFocusChangeListener { _, hasFocus ->
- if (hasFocus) {
- previewViewpager?.apply {
- setCurrentItem(currentItem + 1, true)
- }
- itemView.home_preview_info_btt?.requestFocus()
- }
- }
-
- itemView.home_preview_hidden_prev_focus?.setOnFocusChangeListener { _, hasFocus ->
- if (hasFocus) {
- previewViewpager?.apply {
- if (currentItem <= 0) {
- nav_rail_view?.menu?.getItem(0)?.actionView?.requestFocus()
- } else {
- setCurrentItem(currentItem - 1, true)
- itemView.home_preview_play_btt?.requestFocus()
- }
- }
- }
- }
- // very ugly code, but I dont care
- val watchType =
- DataStoreHelper.getResultWatchState(this.getId())
- itemView.home_preview_bookmark?.setText(watchType.stringRes)
- itemView.home_preview_bookmark?.setCompoundDrawablesWithIntrinsicBounds(
- null,
- ContextCompat.getDrawable(
- itemView.home_preview_bookmark.context,
- watchType.iconRes
- ),
- null,
- null
- )
- itemView.home_preview_bookmark?.setOnClickListener { fab ->
- fab.context.getActivity()?.showBottomDialog(
- WatchType.values()
- .map { fab.context.getString(it.stringRes) }
- .toList(),
- DataStoreHelper.getResultWatchState(this.getId()).ordinal,
- fab.context.getString(R.string.action_add_to_bookmarks),
- showApply = false,
- {}) {
- val newValue = WatchType.values()[it]
- itemView.home_preview_bookmark?.setCompoundDrawablesWithIntrinsicBounds(
- null,
- ContextCompat.getDrawable(
- itemView.home_preview_bookmark.context,
- newValue.iconRes
- ),
- null,
- null
- )
- itemView.home_preview_bookmark?.setText(newValue.stringRes)
-
- ResultViewModel2.updateWatchStatus(
- this,
- newValue
- )
- reloadStored()
- }
- }
- }
- }
- }
-
- private var resumeAdapter: HomeChildItemAdapter? = null
- private var resumeHolder: View? = itemView.home_watch_holder
- private var resumeRecyclerView: RecyclerView? = itemView.home_watch_child_recyclerview
-
- private var bookmarkHolder: View? = itemView.home_bookmarked_holder
- private var bookmarkAdapter: HomeChildItemAdapter? = null
- private var bookmarkRecyclerView: RecyclerView? =
- itemView.home_bookmarked_child_recyclerview
-
- fun onViewDetachedFromWindow() {
- previewViewpager?.unregisterOnPageChangeCallback(previewCallback)
- }
-
- fun onViewAttachedToWindow() {
- previewViewpager?.registerOnPageChangeCallback(previewCallback)
- }
-
- private val toggleList = listOf(
- Pair(itemView.home_type_watching_btt, WatchType.WATCHING),
- Pair(itemView.home_type_completed_btt, WatchType.COMPLETED),
- Pair(itemView.home_type_dropped_btt, WatchType.DROPPED),
- Pair(itemView.home_type_on_hold_btt, WatchType.ONHOLD),
- Pair(itemView.home_plan_to_watch_btt, WatchType.PLANTOWATCH),
- )
-
- init {
- itemView.home_preview_change_api?.setOnClickListener { view ->
- changeHomePageCallback(view)
- }
- itemView.home_preview_change_api2?.setOnClickListener { view ->
- changeHomePageCallback(view)
- }
-
- previewViewpager?.apply {
- //if (!isTvSettings())
- setPageTransformer(HomeScrollTransformer())
- //else
- // setPageTransformer(null)
-
- if (adapter == null)
- adapter = HomeScrollAdapter(
- if (isTvSettings()) R.layout.home_scroll_view_tv else R.layout.home_scroll_view,
- if (isTvSettings()) true else null
- )
- }
- previewAdapter = previewViewpager?.adapter as? HomeScrollAdapter?
- // previewViewpager?.registerOnPageChangeCallback(previewCallback)
-
- if (resumeAdapter == null) {
- resumeRecyclerView?.adapter = HomeChildItemAdapter(
- ArrayList(),
- nextFocusUp = itemView.nextFocusUpId,
- nextFocusDown = itemView.nextFocusDownId
- ) { callback ->
- if (callback.action != SEARCH_ACTION_SHOW_METADATA) {
- searchClickCallback(callback)
- return@HomeChildItemAdapter
- }
- callback.view.context?.getActivity()?.showOptionSelectStringRes(
- callback.view,
- callback.card.posterUrl,
- listOf(
- R.string.action_open_watching,
- R.string.action_remove_watching
- ),
- listOf(
- R.string.action_open_play,
- R.string.action_open_watching,
- R.string.action_remove_watching
- )
- ) { (isTv, actionId) ->
- when (actionId + if (isTv) 0 else 1) {
- // play
- 0 -> {
- searchClickCallback.invoke(
- SearchClickCallback(
- START_ACTION_RESUME_LATEST,
- callback.view,
- -1,
- callback.card
- )
- )
- reloadStored()
- }
- //info
- 1 -> {
- searchClickCallback(
- SearchClickCallback(
- SEARCH_ACTION_LOAD,
- callback.view,
- -1,
- callback.card
- )
- )
-
- reloadStored()
- }
- // remove
- 2 -> {
- val card = callback.card
- if (card is DataStoreHelper.ResumeWatchingResult) {
- DataStoreHelper.removeLastWatched(card.parentId)
- reloadStored()
- }
- }
- }
- }
- }
- }
- resumeAdapter = resumeRecyclerView?.adapter as? HomeChildItemAdapter
- if (bookmarkAdapter == null) {
- bookmarkRecyclerView?.adapter = HomeChildItemAdapter(
- ArrayList(),
- nextFocusUp = itemView.nextFocusUpId,
- nextFocusDown = itemView.nextFocusDownId
- ) { callback ->
- if (callback.action != SEARCH_ACTION_SHOW_METADATA) {
- searchClickCallback(callback)
- return@HomeChildItemAdapter
- }
- callback.view.context?.getActivity()?.showOptionSelectStringRes(
- callback.view,
- callback.card.posterUrl,
- listOf(
- R.string.action_open_watching,
- R.string.action_remove_from_bookmarks,
- ),
- listOf(
- R.string.action_open_play,
- R.string.action_open_watching,
- R.string.action_remove_from_bookmarks
- )
- ) { (isTv, actionId) ->
- when (actionId + if (isTv) 0 else 1) { // play
- 0 -> {
- searchClickCallback.invoke(
- SearchClickCallback(
- START_ACTION_RESUME_LATEST,
- callback.view,
- -1,
- callback.card
- )
- )
- reloadStored()
- }
- 1 -> { // info
- searchClickCallback(
- SearchClickCallback(
- SEARCH_ACTION_LOAD,
- callback.view,
- -1,
- callback.card
- )
- )
-
- reloadStored()
- }
- 2 -> { // remove
- DataStoreHelper.setResultWatchState(
- callback.card.id,
- WatchType.NONE.internalId
- )
- reloadStored()
- }
- }
- }
- }
- }
- bookmarkAdapter = bookmarkRecyclerView?.adapter as? HomeChildItemAdapter
-
- for ((chip, watch) in toggleList) {
- chip?.isChecked = false
- chip?.setOnCheckedChangeListener { _, isChecked ->
- if (isChecked) {
- loadStoredData(
- setOf(watch)
- // If we filter all buttons then two can be checked at the same time
- // Revert this if you want to go back to multi selection
-// toggleList.filter { it.first?.isChecked == true }.map { it.second }.toSet()
- )
- }
- // Else if all are unchecked -> Do not load data
- else if (toggleList.all { it.first?.isChecked != true }) {
- loadStoredData(emptySet())
- }
- }
- }
-
- itemView.home_search?.context?.fixPaddingStatusbar(itemView.home_search)
-
- itemView.home_search?.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
- override fun onQueryTextSubmit(query: String): Boolean {
- searchQueryCallback.invoke(false to query)
- //QuickSearchFragment.pushSearch(activity, query, currentApiName?.let { arrayOf(it) }
- return true
- }
-
- override fun onQueryTextChange(newText: String): Boolean {
- searchQueryCallback.invoke(true to newText)
- //searchViewModel.quickSearch(newText)
- return true
- }
- })
- }
-
- fun updateApiName(name: String) {
- itemView.home_preview_change_api2?.text = name
- itemView.home_preview_change_api?.text = name
- }
-
- fun updatePreview(preview: Resource>>) {
- itemView.home_preview_change_api2?.isGone = preview is Resource.Success
- if (preview is Resource.Success) {
- itemView.home_none_padding?.apply {
- val params = layoutParams
- params.height = 0
- layoutParams = params
- }
- } else {
- itemView.home_none_padding?.context?.fixPaddingStatusbarView(itemView.home_none_padding)
- }
- when (preview) {
- is Resource.Success -> {
- if (true != previewAdapter?.setItems(
- preview.value.second,
- preview.value.first
- )
- ) {
- // this might seam weird and useless, however this prevents a very weird andrid bug were the viewpager is not rendered properly
- // I have no idea why that happens, but this is my ducktape solution
- previewViewpager?.setCurrentItem(0, false)
- previewViewpager?.beginFakeDrag()
- previewViewpager?.fakeDragBy(1f)
- previewViewpager?.endFakeDrag()
- previewCallback.onPageSelected(0)
- previewHeader?.isVisible = true
- }
- }
- else -> {
- previewAdapter?.setItems(listOf(), false)
- previewViewpager?.setCurrentItem(0, false)
- previewHeader?.isVisible = false
- }
- }
- // previewViewpager?.postDelayed({ previewViewpager?.scr(100, 0) }, 1000)
- //previewViewpager?.postInvalidate()
- }
-
- fun updateResume(resumeWatching: List) {
- resumeHolder?.isVisible = resumeWatching.isNotEmpty()
- resumeAdapter?.updateList(resumeWatching)
-
- if (!isTvSettings()) {
- itemView.home_watch_parent_item_title?.setOnClickListener {
- moreInfoClickCallback.invoke(
- HomeViewModel.ExpandableHomepageList(
- HomePageList(
- itemView.home_watch_parent_item_title?.text.toString(),
- resumeWatching,
- false
- ), 1, false
- )
- )
- }
- }
- }
-
- fun updateBookmarks(data: Pair>) {
- bookmarkHolder?.isVisible = data.first
- bookmarkAdapter?.updateList(data.second)
- if (!isTvSettings()) {
- itemView.home_bookmark_parent_item_title?.setOnClickListener {
- val items = toggleList.mapNotNull { it.first }.filter { it.isChecked }
- if (items.isEmpty()) return@setOnClickListener // we don't want to show an empty dialog
- val textSum = items
- .mapNotNull { it.text }.joinToString()
-
- moreInfoClickCallback.invoke(
- HomeViewModel.ExpandableHomepageList(
- HomePageList(
- textSum,
- data.second,
- false
- ), 1, false
- )
- )
- }
- }
- }
-
- fun setAvailableWatchStatusTypes(availableWatchStatusTypes: Pair, Set>) {
- for ((chip, watch) in toggleList) {
- chip?.apply {
- isVisible = availableWatchStatusTypes.second.contains(watch)
- isChecked = availableWatchStatusTypes.first.contains(watch)
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt
deleted file mode 100644
index f296e53d..00000000
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt
+++ /dev/null
@@ -1,103 +0,0 @@
-package com.lagradost.cloudstream3.ui.home
-
-import android.content.res.Configuration
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.annotation.LayoutRes
-import androidx.core.view.isGone
-import androidx.recyclerview.widget.DiffUtil
-import androidx.recyclerview.widget.RecyclerView
-import com.lagradost.cloudstream3.LoadResponse
-import com.lagradost.cloudstream3.R
-import com.lagradost.cloudstream3.utils.UIHelper.setImage
-import kotlinx.android.synthetic.main.fragment_home_head_tv.*
-import kotlinx.android.synthetic.main.fragment_home_head_tv.view.*
-import kotlinx.android.synthetic.main.home_scroll_view.view.*
-
-
-class HomeScrollAdapter(
- @LayoutRes val layout: Int = R.layout.home_scroll_view,
- private val forceHorizontalPosters: Boolean? = null
-) : RecyclerView.Adapter() {
- private var items: MutableList = mutableListOf()
- var hasMoreItems: Boolean = false
-
- fun getItem(position: Int): LoadResponse? {
- return items.getOrNull(position)
- }
-
- fun setItems(newItems: List, hasNext: Boolean): Boolean {
- val isSame = newItems.firstOrNull()?.url == items.firstOrNull()?.url
- hasMoreItems = hasNext
-
- val diffResult = DiffUtil.calculateDiff(
- HomeScrollDiffCallback(this.items, newItems)
- )
-
- items.clear()
- items.addAll(newItems)
-
-
- diffResult.dispatchUpdatesTo(this)
-
- return isSame
- }
-
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
- return CardViewHolder(
- LayoutInflater.from(parent.context).inflate(layout, parent, false),
- forceHorizontalPosters
- )
- }
-
- override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
- when (holder) {
- is CardViewHolder -> {
- holder.bind(items[position])
- }
- }
- }
-
- class CardViewHolder
- constructor(
- itemView: View,
- private val forceHorizontalPosters: Boolean? = null
- ) :
- RecyclerView.ViewHolder(itemView) {
-
- fun bind(card: LoadResponse) {
- card.apply {
- val isHorizontal =
- (forceHorizontalPosters == true) || ((forceHorizontalPosters != false) && itemView.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE)
-
- val posterUrl = if (isHorizontal) backgroundPosterUrl ?: posterUrl else posterUrl
- ?: backgroundPosterUrl
- itemView.home_scroll_preview_tags?.text = tags?.joinToString(" • ") ?: ""
- itemView.home_scroll_preview_tags?.isGone = tags.isNullOrEmpty()
- itemView.home_scroll_preview?.setImage(posterUrl, posterHeaders)
- itemView.home_scroll_preview_title?.text = name
- }
- }
- }
-
- class HomeScrollDiffCallback(
- private val oldList: List,
- private val newList: List