diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index 250734cd..cd3c2574 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,8 +1,8 @@
blank_issues_enabled: false
contact_links:
- - name: Request a new provider or report bug with an existing provider
+ - name: Report provider bug
url: https://github.com/recloudstream
- about: Please do not report any provider bugs here or request new providers. This repository does not contain any providers. Please find the appropriate repository and report your issue there or join the discord.
+ about: 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.
- name: Discord
url: https://discord.gg/5Hus6fM
about: Join our discord for faster support on smaller issues.
diff --git a/.github/downloads.jpg b/.github/downloads.jpg
new file mode 100644
index 00000000..0b671edc
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..2ccfaff4
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..0580fb03
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..5e63169f
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..998b7753
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/generate_dokka.yml b/.github/workflows/generate_dokka.yml
index 3c5caad7..032ea8d0 100644
--- a/.github/workflows/generate_dokka.yml
+++ b/.github/workflows/generate_dokka.yml
@@ -39,8 +39,9 @@ jobs:
- name: Clean old builds
run: |
+ shopt -s extglob
cd $GITHUB_WORKSPACE/dokka/
- rm -rf "./-cloudstream"
+ rm -rf !(.git)
- name: Setup JDK 11
uses: actions/setup-java@v1
diff --git a/.github/workflows/issue_action.yml b/.github/workflows/issue-action.yml
similarity index 69%
rename from .github/workflows/issue_action.yml
rename to .github/workflows/issue-action.yml
index 108cec82..bfcb10d0 100644
--- a/.github/workflows/issue_action.yml
+++ b/.github/workflows/issue-action.yml
@@ -1,88 +1,63 @@
-name: Issue automatic actions
-
-on:
- issues:
- types: [opened]
-
-jobs:
- issue-moderator:
- 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 }}
- - name: Similarity analysis
- id: similarity
- uses: actions-cool/issues-similarity-analysis@v1
- with:
- token: ${{ steps.generate_token.outputs.token }}
- filter-threshold: 0.60
- 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
- with:
- github-token: ${{ steps.generate_token.outputs.token }}
- issue-close-message: |
- @${issue.user.login}: hello! :wave:
- This issue is being automatically closed because it does not follow the issue template."
- closed-issues-label: "invalid"
- - name: Check if issue mentions a provider
- id: provider_check
- env:
- GH_TEXT: "${{ github.event.issue.title }} ${{ github.event.issue.body }}"
- run: |
- 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
- - name: Comment if issue mentions a provider
- if: steps.provider_check.outputs.name != 'none'
- uses: actions-cool/issues-helper@v3
- with:
- actions: 'create-comment'
- token: ${{ steps.generate_token.outputs.token }}
- body: |
- Hello ${{ github.event.issue.user.login }}.
- 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:
- type: 'issue'
- token: ${{ steps.generate_token.outputs.token }}
- emoji: 'eyes'
-
-
+name: Issue automatic actions
+
+on:
+ issues:
+ types: [opened, edited]
+
+jobs:
+ issue-moderator:
+ 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 }}
+ - name: Similarity analysis
+ uses: actions-cool/issues-similarity-analysis@v1
+ with:
+ token: ${{ steps.generate_token.outputs.token }}
+ filter-threshold: 0.5
+ title-excludes: ''
+ comment-title: |
+ ### Your issue looks similar to these issues:
+ Please close if duplicate.
+ comment-body: '${index}. ${similarity} #${number}'
+ - uses: actions/checkout@v2
+ - name: Automatically close issues that dont follow the issue template
+ uses: lucasbento/auto-close-issues@v1.0.2
+ with:
+ github-token: ${{ steps.generate_token.outputs.token }}
+ issue-close-message: |
+ @${issue.user.login}: hello! :wave:
+ This issue is being automatically closed because it does not follow the issue template."
+ closed-issues-label: "invalid"
+ - name: Check if issue mentions a provider
+ id: provider_check
+ env:
+ GH_TEXT: "${{ github.event.issue.title }} ${{ github.event.issue.body }}"
+ run: |
+ 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 "::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
+ with:
+ actions: 'create-comment'
+ token: ${{ steps.generate_token.outputs.token }}
+ body: |
+ Hello ${{ github.event.issue.user.login }}.
+ 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: Add eyes reaction to all issues
+ uses: actions-cool/emoji-helper@v1.0.0
+ with:
+ type: 'issue'
+ token: ${{ steps.generate_token.outputs.token }}
+ emoji: 'eyes'
+
+
diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml
index 4ce7dba1..71301e25 100644
--- a/.github/workflows/prerelease.yml
+++ b/.github/workflows/prerelease.yml
@@ -40,10 +40,12 @@ 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
+ ./gradlew assemblePrerelease
+ ./gradlew androidSourcesJar
+ ./gradlew makeJar
env:
SIGNING_KEY_ALIAS: "key0"
SIGNING_KEY_PASSWORD: ${{ steps.fetch_keystore.outputs.key_pwd }}
@@ -53,9 +55,9 @@ jobs:
with:
repo_token: "${{ secrets.GITHUB_TOKEN }}"
automatic_release_tag: "pre-release"
- prerelease: true
+ prerelease: false
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..0035daf7 100644
--- a/README.md
+++ b/README.md
@@ -1,18 +1,44 @@
# 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)
+[](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
+* 🇨🇿 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..1aa62378
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,215 @@
+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 50
+ versionName "3.1.3"
+
+ 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'
+
+ // 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.2'
+
+ // 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
+}
+
+task makeJar(type: Copy) {
+ // after modifying here, you can export. Jar
+ from('build/intermediates/compile_app_classes_jar/debug')
+ into('build') // output location
+ include('classes.jar') // the classes file of the imported rack package
+ dependsOn build
+}
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/debug/ic_launcher-playstore.png b/app/src/debug/ic_launcher-playstore.png
index 8c374dd9..3c4e788c 100644
Binary files a/app/src/debug/ic_launcher-playstore.png and b/app/src/debug/ic_launcher-playstore.png differ
diff --git a/app/src/debug/res/mipmap-hdpi/ic_launcher.png b/app/src/debug/res/mipmap-hdpi/ic_launcher.png
index c947f526..bf8e595f 100644
Binary files a/app/src/debug/res/mipmap-hdpi/ic_launcher.png and b/app/src/debug/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/debug/res/mipmap-hdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-hdpi/ic_launcher_round.png
index c947f526..bf8e595f 100644
Binary files a/app/src/debug/res/mipmap-hdpi/ic_launcher_round.png and b/app/src/debug/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/app/src/debug/res/mipmap-mdpi/ic_launcher.png b/app/src/debug/res/mipmap-mdpi/ic_launcher.png
index e841896f..935b7108 100644
Binary files a/app/src/debug/res/mipmap-mdpi/ic_launcher.png and b/app/src/debug/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/debug/res/mipmap-mdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-mdpi/ic_launcher_round.png
index e841896f..935b7108 100644
Binary files a/app/src/debug/res/mipmap-mdpi/ic_launcher_round.png and b/app/src/debug/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/app/src/debug/res/mipmap-xhdpi/ic_banner.png b/app/src/debug/res/mipmap-xhdpi/ic_banner.png
index 6e23cfcf..16c4fdd1 100644
Binary files a/app/src/debug/res/mipmap-xhdpi/ic_banner.png and b/app/src/debug/res/mipmap-xhdpi/ic_banner.png differ
diff --git a/app/src/debug/res/mipmap-xhdpi/ic_launcher.png b/app/src/debug/res/mipmap-xhdpi/ic_launcher.png
index c80f9a10..d62f3f79 100644
Binary files a/app/src/debug/res/mipmap-xhdpi/ic_launcher.png and b/app/src/debug/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png
index c80f9a10..d62f3f79 100644
Binary files a/app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png and b/app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png b/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png
index f0b781bb..38d6ede0 100644
Binary files a/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png and b/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png
index f0b781bb..38d6ede0 100644
Binary files a/app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png and b/app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png
index d5fa9d70..81c5621b 100644
Binary files a/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png and b/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png
index d5fa9d70..81c5621b 100644
Binary files a/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png and b/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 563c82f8..460a47ea 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 +103,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -183,10 +138,6 @@
android:name=".ui.ControllerActivity"
android:exported="false" />
-
-
Unit)) :
- Thread.UncaughtExceptionHandler {
- override fun uncaughtException(thread: Thread, error: Throwable) {
- ACRA.errorReporter.handleException(error)
- try {
- PrintStream(errorFile).use { ps ->
- ps.println(String.format("Currently loading extension: ${PluginManager.currentlyLoading ?: "none"}"))
- ps.println(
- String.format(
- "Fatal exception on thread %s (%d)",
- thread.name,
- thread.id
- )
- )
- error.printStackTrace(ps)
- }
- } catch (ignored: FileNotFoundException) {
- }
- try {
- onError.invoke()
- } catch (ignored: Exception) {
- }
- exitProcess(1)
- }
-
-}
-
class AcraApplication : Application() {
- override fun onCreate() {
- super.onCreate()
- Thread.setDefaultUncaughtExceptionHandler(ExceptionHandler(filesDir.resolve("last_error")) {
- val intent = context!!.packageManager.getLaunchIntentForPackage(context!!.packageName)
- startActivity(Intent.makeRestartActivityTask(intent!!.component))
- })
- }
-
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
context = base
@@ -194,15 +148,5 @@ class AcraApplication : Application() {
fun openBrowser(url: String, fallbackWebview: Boolean = false, fragment: Fragment? = null) {
context?.openBrowser(url, fallbackWebview, fragment)
}
-
- /** Will fallback to webview if in TV layout */
- fun openBrowser(url: String, activity: FragmentActivity?) {
- openBrowser(
- url,
- isTvSettings(),
- activity?.supportFragmentManager?.fragments?.lastOrNull()
- )
- }
-
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt
index 89f0ae51..0f54770f 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt
@@ -1,38 +1,31 @@
package com.lagradost.cloudstream3
-import android.Manifest
import android.app.Activity
import android.app.PictureInPictureParams
import android.content.Context
import android.content.pm.PackageManager
import android.content.res.Resources
import android.os.Build
+import android.os.Looper
import android.util.Log
import android.view.*
import android.widget.TextView
import android.widget.Toast
-import androidx.activity.ComponentActivity
-import androidx.activity.result.contract.ActivityResultContracts
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
-import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.player.PlayerEventType
-import com.lagradost.cloudstream3.ui.result.ResultFragment
import com.lagradost.cloudstream3.ui.result.UiText
-import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.updateTv
-import com.lagradost.cloudstream3.utils.DataStoreHelper
import com.lagradost.cloudstream3.utils.Event
import com.lagradost.cloudstream3.utils.UIHelper
import com.lagradost.cloudstream3.utils.UIHelper.hasPIPPermission
import com.lagradost.cloudstream3.utils.UIHelper.shouldShowPIPMode
import com.lagradost.cloudstream3.utils.UIHelper.toPx
+import kotlinx.coroutines.currentCoroutineContext
import org.schabi.newpipe.extractor.NewPipe
import java.util.*
@@ -42,7 +35,6 @@ object CommonActivity {
return (this as MainActivity?)?.mSessionManager?.currentCastSession
}
-
var canEnterPipMode: Boolean = false
var canShowPipMode: Boolean = false
var isInPIPMode: Boolean = false
@@ -63,9 +55,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 +63,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 +99,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)
@@ -138,7 +118,7 @@ object CommonActivity {
setLocale(this, localeCode)
}
- fun init(act: ComponentActivity?) {
+ fun init(act: Activity?) {
if (act == null) return
//https://stackoverflow.com/questions/52594181/how-to-know-if-user-has-disabled-picture-in-picture-feature-permission
//https://developer.android.com/guide/topics/ui/picture-in-picture
@@ -148,41 +128,8 @@ object CommonActivity {
act.hasPIPPermission() // CHECK IF FEATURE IS ENABLED IN SETTINGS
act.updateLocale()
- act.updateTv()
+
NewPipe.init(DownloaderTestImpl.getInstance())
-
- for (resumeApp in resumeApps) {
- resumeApp.launcher =
- act.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
- 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)
- if (dur > 0L && pos > 0L)
- DataStoreHelper.setViewPos(getKey(resumeApp.lastId), pos, dur)
- removeKey(resumeApp.lastId)
- ResultFragment.updateUI()
- }
- }
- }
-
- // 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() {
@@ -220,8 +167,6 @@ object CommonActivity {
"Light" -> R.style.LightMode
"Amoled" -> R.style.AmoledMode
"AmoledLight" -> R.style.AmoledModeLight
- "Monet" -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
- R.style.MonetMode else R.style.AppTheme
else -> R.style.AppTheme
}
@@ -242,10 +187,6 @@ object CommonActivity {
"Banana" -> R.style.OverlayPrimaryColorBanana
"Party" -> R.style.OverlayPrimaryColorParty
"Pink" -> R.style.OverlayPrimaryColorPink
- "Monet" -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
- R.style.OverlayPrimaryColorMonet else R.style.OverlayPrimaryColorNormal
- "Monet2" -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
- R.style.OverlayPrimaryColorMonetTwo else R.style.OverlayPrimaryColorNormal
else -> R.style.OverlayPrimaryColorNormal
}
act.theme.applyStyle(currentTheme, true)
@@ -343,7 +284,7 @@ object CommonActivity {
KeyEvent.KEYCODE_MEDIA_PLAY, KeyEvent.KEYCODE_BUTTON_START -> {
PlayerEventType.Play
}
- KeyEvent.KEYCODE_L, KeyEvent.KEYCODE_NUMPAD_7, KeyEvent.KEYCODE_7 -> {
+ KeyEvent.KEYCODE_L, KeyEvent.KEYCODE_NUMPAD_7 -> {
PlayerEventType.Lock
}
KeyEvent.KEYCODE_H, KeyEvent.KEYCODE_MENU -> {
@@ -352,25 +293,22 @@ object CommonActivity {
KeyEvent.KEYCODE_M, KeyEvent.KEYCODE_VOLUME_MUTE -> {
PlayerEventType.ToggleMute
}
- KeyEvent.KEYCODE_S, KeyEvent.KEYCODE_NUMPAD_9, KeyEvent.KEYCODE_9 -> {
+ KeyEvent.KEYCODE_S, KeyEvent.KEYCODE_NUMPAD_9 -> {
PlayerEventType.ShowMirrors
}
// OpenSubtitles shortcut
- KeyEvent.KEYCODE_O, KeyEvent.KEYCODE_NUMPAD_8, KeyEvent.KEYCODE_8 -> {
+ KeyEvent.KEYCODE_O, KeyEvent.KEYCODE_NUMPAD_8 -> {
PlayerEventType.SearchSubtitlesOnline
}
- KeyEvent.KEYCODE_E, KeyEvent.KEYCODE_NUMPAD_3, KeyEvent.KEYCODE_3 -> {
+ KeyEvent.KEYCODE_E, KeyEvent.KEYCODE_NUMPAD_3 -> {
PlayerEventType.ShowSpeed
}
- KeyEvent.KEYCODE_R, KeyEvent.KEYCODE_NUMPAD_0, KeyEvent.KEYCODE_0 -> {
+ KeyEvent.KEYCODE_R, KeyEvent.KEYCODE_NUMPAD_0 -> {
PlayerEventType.Resize
}
- KeyEvent.KEYCODE_C, KeyEvent.KEYCODE_NUMPAD_4, KeyEvent.KEYCODE_4 -> {
+ KeyEvent.KEYCODE_C, KeyEvent.KEYCODE_NUMPAD_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 +387,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..67283de3 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
@@ -46,8 +40,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 +53,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 +73,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 +151,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 +192,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,35 +226,23 @@ object APIHolder {
}
private fun Context.getHasTrailers(): Boolean {
+ if (this.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)
- .getStringSet(this.getString(R.string.prefer_media_type_key), defaultSet)
- ?.mapNotNull { it.toIntOrNull() ?: return@mapNotNull null }
+ .getStringSet(this.getString(R.string.prefer_media_type_key), defaultSet)
+ ?.mapNotNull { it.toIntOrNull() ?: return@mapNotNull null }
} catch (e: Throwable) {
null
} ?: default
val langs = this.getApiProviderLangSettings()
- val hasUniversal = langs.contains(AllLanguagesName)
- val allApis = 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 +295,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
@@ -438,32 +315,17 @@ data class ProvidersInfoJson(
@JsonProperty("status") var status: Int,
)
-data class SettingsJson(
- @JsonProperty("enableAdult") var enableAdult: Boolean = false,
-)
-
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 +334,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 +342,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())
}
@@ -503,7 +354,6 @@ fun newHomePageResponse(list: List, hasNext: Boolean? = null): Hom
abstract class MainAPI {
companion object {
var overrideData: HashMap? = null
- var settingsForProvider: SettingsJson = SettingsJson()
}
fun init() {
@@ -525,19 +375,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 +397,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 +408,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 +466,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 +551,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? {
@@ -1089,11 +891,8 @@ data class TvSeriesSearchResponse(
) : SearchResponse
data class TrailerData(
- val extractorUrl: String,
- val referer: String?,
- val raw: Boolean,
- //var mirros: List,
- //var subtitles: List = emptyList(),
+ var mirros: List,
+ var subtitles: List = emptyList(),
)
interface LoadResponse {
@@ -1172,8 +971,7 @@ interface LoadResponse {
addRaw: Boolean = false
) {
if (!isTrailersEnabled || trailerUrl.isNullOrBlank()) return
- this.trailers.add(TrailerData(trailerUrl, referer, addRaw))
- /*val links = arrayListOf()
+ val links = arrayListOf()
val subs = arrayListOf()
if (!loadExtractor(
trailerUrl,
@@ -1197,13 +995,12 @@ interface LoadResponse {
)
} else {
this.trailers.add(TrailerData(links, subs))
- }*/
+ }
}
- /*
fun LoadResponse.addTrailer(newTrailers: List) {
trailers.addAll(newTrailers.map { TrailerData(listOf(it)) })
- }*/
+ }
suspend fun LoadResponse.addTrailer(
trailerUrls: List?,
@@ -1211,8 +1008,7 @@ interface LoadResponse {
addRaw: Boolean = false
) {
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(
@@ -1235,7 +1031,7 @@ interface LoadResponse {
links to subs
}
}.map { (links, subs) -> TrailerData(links, subs) }
- this.trailers.addAll(trailers)*/
+ this.trailers.addAll(trailers)
}
fun LoadResponse.addImdbId(id: String?) {
@@ -1273,43 +1069,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 +1098,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)
}
@@ -1336,11 +1107,6 @@ data class NextAiring(
val unixTime: Long,
)
-/**
- * @param season To be mapped with episode season, not shown in UI if displaySeason is defined
- * @param name To be shown next to the season like "Season $displaySeason $name" but if displaySeason is null then "$name"
- * @param displaySeason What to be displayed next to the season name, if null then the name is the only thing shown.
- * */
data class SeasonData(
val season: Int,
val name: String? = null,
@@ -1351,7 +1117,6 @@ interface EpisodeResponse {
var showStatus: ShowStatus?
var nextAiring: NextAiring?
var seasonNames: List?
- fun getLatestEpisodes(): Map
}
@JvmName("addSeasonNamesString")
@@ -1420,25 +1185,11 @@ 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.
- * */
fun AnimeLoadResponse.addEpisodes(status: DubStatus, episodes: List?) {
if (episodes.isNullOrEmpty()) return
- this.episodes[status] = (this.episodes[status] ?: emptyList()) + episodes
+ this.episodes[status] = episodes
}
suspend fun MainAPI.newAnimeLoadResponse(
@@ -1629,17 +1380,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 +1412,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..236c7912 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
@@ -1,25 +1,18 @@
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.os.Bundle
-import android.util.AttributeSet
import android.util.Log
-import android.view.*
-import android.widget.Toast
-import androidx.activity.result.ActivityResultLauncher
+import android.view.KeyEvent
+import android.view.Menu
+import android.view.MenuItem
+import android.view.WindowManager
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,187 +25,81 @@ 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
import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings
import com.lagradost.cloudstream3.APIHolder.initAll
import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
-import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
-import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.CommonActivity.loadThemes
import com.lagradost.cloudstream3.CommonActivity.onColorSelectedEvent
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.result.ResultFragment
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.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.removeKey
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.Event
+import com.lagradost.cloudstream3.utils.IOnBackPressed
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.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
-//https://wiki.videolan.org/Android_Player_Intents/
-
-//https://github.com/mpv-android/mpv-android/blob/0eb3cdc6f1632636b9c30d52ec50e4b017661980/app/src/main/java/is/xyz/mpv/MPVActivity.kt#L904
-//https://mpv-android.github.io/mpv-android/intent.html
-
-// https://www.webvideocaster.com/integrations
-
-//https://github.com/jellyfin/jellyfin-android/blob/6cbf0edf84a3da82347c8d59b5d5590749da81a9/app/src/main/java/org/jellyfin/mobile/bridge/ExternalPlayer.kt#L225
-
const val VLC_PACKAGE = "org.videolan.vlc"
-const val MPV_PACKAGE = "is.xyz.mpv"
-const val WEB_VIDEO_CAST_PACKAGE = "com.instantbits.cast.webvideo"
+const val VLC_INTENT_ACTION_RESULT = "org.videolan.vlc.player.result"
+val VLC_COMPONENT: ComponentName =
+ ComponentName(VLC_PACKAGE, "org.videolan.vlc.gui.video.VideoPlayerActivity")
+const val VLC_REQUEST_CODE = 42
-val VLC_COMPONENT = ComponentName(VLC_PACKAGE, "$VLC_PACKAGE.gui.video.VideoPlayerActivity")
-val MPV_COMPONENT = ComponentName(MPV_PACKAGE, "$MPV_PACKAGE.MPVActivity")
-
-//TODO REFACTOR AF
-open class ResultResume(
- val packageString: String,
- val action: String = Intent.ACTION_VIEW,
- val position: String? = null,
- val duration: String? = null,
- var launcher: ActivityResultLauncher? = null,
-) {
- val defaultTime = -1L
-
- val lastId get() = "${packageString}_last_open_id"
- suspend fun launch(id: Int?, callback: suspend Intent.() -> Unit) {
- val intent = Intent(action)
-
- if (id != null)
- setKey(lastId, id)
- else
- removeKey(lastId)
-
- intent.setPackage(packageString)
- callback.invoke(intent)
- launcher?.launch(intent)
- }
-
- open fun getPosition(intent: Intent?): Long {
- return defaultTime
- }
-
- open fun getDuration(intent: Intent?): Long {
- return defaultTime
- }
-}
-
-val VLC = object : ResultResume(
- VLC_PACKAGE,
- // Android 13 intent restrictions fucks up specifically launching the VLC player
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
- "org.videolan.vlc.player.result"
- } else {
- Intent.ACTION_VIEW
- },
- "extra_position",
- "extra_duration",
-) {
- override fun getPosition(intent: Intent?): Long {
- return intent?.getLongExtra(this.position, defaultTime) ?: defaultTime
- }
-
- override fun getDuration(intent: Intent?): Long {
- return intent?.getLongExtra(this.duration, defaultTime) ?: defaultTime
- }
-}
-
-val MPV = object : ResultResume(
- MPV_PACKAGE,
- //"is.xyz.mpv.MPVActivity.result", // resume not working :pensive:
- position = "position",
- duration = "duration",
-) {
- override fun getPosition(intent: Intent?): Long {
- return intent?.getIntExtra(this.position, defaultTime.toInt())?.toLong() ?: defaultTime
- }
-
- override fun getDuration(intent: Intent?): Long {
- return intent?.getIntExtra(this.duration, defaultTime.toInt())?.toLong() ?: defaultTime
- }
-}
-
-val WEB_VIDEO = ResultResume(WEB_VIDEO_CAST_PACKAGE)
-
-val resumeApps = arrayOf(
- VLC, MPV, WEB_VIDEO
-)
+const val VLC_FROM_START = -1
+const val VLC_FROM_PROGRESS = -2
+const val VLC_EXTRA_POSITION_OUT = "extra_position"
+const val VLC_EXTRA_DURATION_OUT = "extra_duration"
+const val VLC_LAST_ID_KEY = "vlc_last_open_id"
// Short name for requests client to make it nicer to use
@@ -244,151 +131,12 @@ var app = Requests(responseParser = object : ResponseParser {
class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
companion object {
const val TAG = "MAINACT"
+ var context : MainActivity? = null
- /**
- * 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.
- * */
- fun handleAppIntentUrl(
- activity: FragmentActivity?,
- str: String?,
- 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("?")
- println("Repository url: $realUrl")
- loadRepository(realUrl)
- return true
- } else if (str.contains(appString)) {
- for (api in OAuth2Apis) {
- if (str.contains("/${api.redirectUrl}")) {
- ioSafe {
- Log.i(TAG, "handleAppIntent $str")
- val isSuccessful = api.handleRedirect(str)
-
- if (isSuccessful) {
- Log.i(TAG, "authenticated ${api.name}")
- } else {
- Log.i(TAG, "failed to authenticate ${api.name}")
- }
-
- this@with.runOnUiThread {
- try {
- showToast(
- this@with,
- getString(if (isSuccessful) R.string.authenticated_user else R.string.authenticated_user_fail).format(
- api.name
- )
- )
- } catch (e: Exception) {
- logError(e) // format might fail
- }
- }
- }
- 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) {
- 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)
- return true
- } else {
- for (api in apis) {
- if (str.startsWith(api.mainUrl)) {
- loadResult(str, api.name)
- return true
- }
- }
- }
- }
- }
- return false
- }
- }
-
- 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) {
@@ -422,7 +170,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,
@@ -432,34 +179,12 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
R.id.navigation_settings_updates,
R.id.navigation_settings_ui,
R.id.navigation_settings_account,
- R.id.navigation_settings_providers,
+ R.id.navigation_settings_lang,
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 +199,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
@@ -518,7 +238,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
override fun onResume() {
super.onResume()
- afterPluginsLoadedEvent += ::onAllPluginsLoaded
try {
if (isCastApiAvailable()) {
//mCastSession = mSessionManager.currentCastSession
@@ -531,11 +250,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 +280,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() {
@@ -605,12 +297,36 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
}
}
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ if (requestCode == VLC_REQUEST_CODE) {
+ if (resultCode == RESULT_OK && data != null) {
+ val pos: Long =
+ data.getLongExtra(
+ VLC_EXTRA_POSITION_OUT,
+ -1
+ ) //Last position in media when player exited
+ val dur: Long =
+ data.getLongExtra(
+ VLC_EXTRA_DURATION_OUT,
+ -1
+ ) //Last position in media when player exited
+ val id = getKey(VLC_LAST_ID_KEY)
+ println("SET KEY $id at $pos / $dur")
+ if (dur > 0 && pos > 0) {
+ setViewPos(id, pos, dur)
+ }
+ removeKey(VLC_LAST_ID_KEY)
+ ResultFragment.updateUI()
+ }
+ }
+ super.onActivityResult(requestCode, resultCode, data)
+ }
+
override fun onDestroy() {
val broadcastIntent = Intent()
broadcastIntent.action = "restart_service"
broadcastIntent.setClass(this, VideoDownloadRestartReceiver::class.java)
this.sendBroadcast(broadcastIntent)
- afterPluginsLoadedEvent -= ::onAllPluginsLoaded
super.onDestroy()
}
@@ -623,7 +339,56 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
if (intent == null) return
val str = intent.dataString
loadCache()
- handleAppIntentUrl(this, str, false)
+ if (str != null) {
+ if (str.startsWith("https://cs.repo")) {
+ val realUrl = "https://" + str.substringAfter("?")
+ println("Repository url: $realUrl")
+ loadRepository(realUrl)
+ } else if (str.contains(appString)) {
+ for (api in OAuth2Apis) {
+ if (str.contains("/${api.redirectUrl}")) {
+ val activity = this
+ ioSafe {
+ Log.i(TAG, "handleAppIntent $str")
+ val isSuccessful = api.handleRedirect(str)
+
+ if (isSuccessful) {
+ Log.i(TAG, "authenticated ${api.name}")
+ } else {
+ Log.i(TAG, "failed to authenticate ${api.name}")
+ }
+
+ activity.runOnUiThread {
+ try {
+ showToast(
+ activity,
+ getString(if (isSuccessful) R.string.authenticated_user else R.string.authenticated_user_fail).format(
+ api.name
+ )
+ )
+ } catch (e: Exception) {
+ logError(e) // format might fail
+ }
+ }
+ }
+ }
+ }
+ } else if (URI(str).scheme == appStringRepo) {
+ val url = str.replaceFirst(appStringRepo, "https")
+ loadRepository(url)
+ } else {
+ if (str.startsWith(DOWNLOAD_NAVIGATE_TO)) {
+ this.navigate(R.id.navigation_downloads)
+ } else {
+ for (api in apis) {
+ if (str.startsWith(api.mainUrl)) {
+ loadResult(str, api.name)
+ break
+ }
+ }
+ }
+ }
+ }
}
private fun NavDestination.matchDestination(@IdRes destId: Int): Boolean =
@@ -651,85 +416,11 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
}
}
- private val pluginsLock = Mutex()
- private fun onAllPluginsLoaded(success: Boolean = false) {
- ioSafe {
- pluginsLock.withLock {
- // Load cloned sites after plugins have been loaded since clones depend on plugins.
- try {
- getKey>(USER_PROVIDER_API)?.let { list ->
- list.forEach { custom ->
- allProviders.firstOrNull { it.javaClass.simpleName == custom.parentJavaClass }
- ?.let {
- allProviders.add(it.javaClass.newInstance().apply {
- name = custom.name
- lang = custom.lang
- mainUrl = custom.url.trimEnd('/')
- canBeOverridden = false
- })
- }
- }
- }
- // it.hashCode() is not enough to make sure they are distinct
- apis =
- allProviders.distinctBy { it.lang + it.name + it.mainUrl + it.javaClass.name }
- APIHolder.apiMap = null
- } catch (e: Exception) {
- logError(e)
- }
- }
- }
- }
-
- 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?) {
+ context = this
app.initClient(this)
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
- val errorFile = filesDir.resolve("last_error")
- var lastError: String? = null
- if (errorFile.exists() && errorFile.isFile) {
- lastError = errorFile.readText(Charset.defaultCharset())
- errorFile.delete()
- }
-
- val settingsForProvider = SettingsJson()
- settingsForProvider.enableAdult =
- settingsManager.getBoolean(getString(R.string.enable_nsfw_on_providers_key), false)
-
- MainAPI.settingsForProvider = settingsForProvider
-
loadThemes(this)
updateLocale()
super.onCreate(savedInstanceState)
@@ -742,7 +433,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,155 +442,43 @@ 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)
+ ioSafe {
+ getKey(USER_SELECTED_HOMEPAGE_API)?.let { homeApi ->
+ mainPluginsLoadedEvent.invoke(loadSinglePlugin(this@MainActivity, homeApi))
+ } ?: run {
+ mainPluginsLoadedEvent.invoke(false)
+ }
+
+ if (settingsManager.getBoolean(getString(R.string.auto_update_plugins_key), true)) {
+ PluginManager.updateAllOnlinePluginsAndLoadThem(this@MainActivity)
+ } else {
+ PluginManager.loadAllOnlinePlugins(this@MainActivity)
+ }
+
+ PluginManager.loadAllLocalPlugins(this@MainActivity)
+
+ // Load cloned sites after plugins have been loaded since clones depend on plugins.
+ try {
+ getKey>(USER_PROVIDER_API)?.let { list ->
+ list.forEach { custom ->
+ allProviders.firstOrNull { it.javaClass.simpleName == custom.parentJavaClass }
+ ?.let {
+ allProviders.add(it.javaClass.newInstance().apply {
+ name = custom.name
+ lang = custom.lang
+ mainUrl = custom.url.trimEnd('/')
+ canBeOverridden = 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) {
- ioSafe {
- getKey(USER_SELECTED_HOMEPAGE_API)?.let { homeApi ->
- mainPluginsLoadedEvent.invoke(loadSinglePlugin(this@MainActivity, homeApi))
- } ?: run {
- mainPluginsLoadedEvent.invoke(false)
- }
-
- ioSafe {
- if (settingsManager.getBoolean(
- getString(R.string.auto_update_plugins_key),
- true
- )
- ) {
- 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)
}
}
-
- ioSafe {
- PluginManager.loadAllLocalPlugins(this@MainActivity, false)
- }
+ apis = allProviders.distinctBy { it }
+ APIHolder.apiMap = null
+ } catch (e: Exception) {
+ logError(e)
}
- } else {
- val builder: AlertDialog.Builder = AlertDialog.Builder(this)
- builder.setTitle(R.string.safe_mode_title)
- builder.setMessage(R.string.safe_mode_description)
- builder.apply {
- setPositiveButton(R.string.safe_mode_crash_info) { _, _ ->
- val tbBuilder: AlertDialog.Builder = AlertDialog.Builder(context)
- tbBuilder.setTitle(R.string.safe_mode_title)
- tbBuilder.setMessage(lastError)
- tbBuilder.show()
- }
- setNegativeButton("Ok") { _, _ -> }
- }
- builder.show().setDefaultFocus()
- }
-
- 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)
- }
- }
- }
- }
- }
+ afterPluginsLoadedEvent.invoke(true)
}
// ioSafe {
@@ -917,8 +496,10 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
for (api in accountManagers) {
api.init()
}
+ }
- inAppAuths.amap { api ->
+ ioSafe {
+ inAppAuths.apmap { api ->
try {
api.initialize()
} catch (e: Exception) {
@@ -942,17 +523,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 +536,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 +705,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
deleted file mode 100644
index b0051ba7..00000000
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/AStreamHub.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-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
-
-open class AStreamHub : ExtractorApi() {
- override val name = "AStreamHub"
- override val mainUrl = "https://astreamhub.com"
- override val requiresReferer = true
-
- override suspend fun getUrl(url: String, referer: String?): List {
- val sources = mutableListOf()
- app.get(url).document.selectFirst("body > script").let { script ->
- val text = script?.html() ?: ""
- Log.i("Dev", "text => $text")
- if (text.isNotBlank()) {
- val m3link = "(?<=file:)(.*)(?=,)".toRegex().find(text)
- ?.groupValues?.get(0)?.trim()?.trim('"') ?: ""
- Log.i("Dev", "m3link => $m3link")
- if (m3link.isNotBlank()) {
- sources.add(
- ExtractorLink(
- name = name,
- source = name,
- url = m3link,
- isM3u8 = true,
- quality = Qualities.Unknown.value,
- referer = referer ?: url
- )
- )
- }
- }
- }
- return sources
- }
-
-}
\ No newline at end of file
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..c5eaf40e 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/DoodExtractor.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/DoodExtractor.kt
@@ -7,10 +7,6 @@ import com.lagradost.cloudstream3.utils.Qualities
import com.lagradost.cloudstream3.utils.getQualityFromName
import kotlinx.coroutines.delay
-class DoodWfExtractor : DoodLaExtractor() {
- override var mainUrl = "https://dood.wf"
-}
-
class DoodCxExtractor : DoodLaExtractor() {
override var mainUrl = "https://dood.cx"
}
@@ -38,9 +34,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
deleted file mode 100644
index 45a06dcc..00000000
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Embedgram.kt
+++ /dev/null
@@ -1,37 +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.getQualityFromName
-import com.lagradost.cloudstream3.utils.httpsify
-
-open class Embedgram : ExtractorApi() {
- override val name = "Embedgram"
- override val mainUrl = "https://embedgram.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 = referer).document
- val link = document.select("video source:last-child").attr("src")
- val quality = document.select("video source:last-child").attr("title")
- callback.invoke(
- ExtractorLink(
- this.name,
- this.name,
- httpsify(link),
- "$mainUrl/",
- getQualityFromName(quality),
- headers = mapOf(
- "Range" to "bytes=0-"
- )
- )
- )
- }
-}
\ No newline at end of file
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..e36a03d3 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,9 @@ 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.M3u8Helper
-open class GMPlayer : ExtractorApi() {
+class GMPlayer : ExtractorApi() {
override val name = "GM Player"
override val mainUrl = "https://gmplayer.xyz"
override val requiresReferer = true
@@ -25,16 +25,11 @@ open class GMPlayer : ExtractorApi() {
data = mapOf("hash" to id, "r" to ref)
).parsed().videoSource ?: return null
- return listOf(
- ExtractorLink(
- this.name,
- this.name,
- m3u8,
- ref,
- Qualities.Unknown.value,
- headers = mapOf("accept" to "*/*"),
- isM3u8 = true
- )
+ return M3u8Helper.generateM3u8(
+ name,
+ m3u8,
+ ref,
+ headers = mapOf("accept" to "*/*")
)
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/extractors/Gdriveplayer.kt b/app/src/main/java/com/lagradost/cloudstream3/extractors/Gdriveplayer.kt
deleted file mode 100644
index df9c74a4..00000000
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Gdriveplayer.kt
+++ /dev/null
@@ -1,209 +0,0 @@
-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
-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() {
- override val mainUrl: String = "https://gdriveplayerapi.com"
-}
-
-class Gdriveplayerapp : Gdriveplayer() {
- override val mainUrl: String = "https://gdriveplayer.app"
-}
-
-class Gdriveplayerfun : Gdriveplayer() {
- override val mainUrl: String = "https://gdriveplayer.fun"
-}
-
-class Gdriveplayerio : Gdriveplayer() {
- override val mainUrl: String = "https://gdriveplayer.io"
-}
-
-class Gdriveplayerme : Gdriveplayer() {
- override val mainUrl: String = "https://gdriveplayer.me"
-}
-
-class Gdriveplayerbiz : Gdriveplayer() {
- override val mainUrl: String = "https://gdriveplayer.biz"
-}
-
-class Gdriveplayerorg : Gdriveplayer() {
- override val mainUrl: String = "https://gdriveplayer.org"
-}
-
-class Gdriveplayerus : Gdriveplayer() {
- override val mainUrl: String = "https://gdriveplayer.us"
-}
-
-class Gdriveplayerco : Gdriveplayer() {
- override val mainUrl: String = "https://gdriveplayer.co"
-}
-
-open class Gdriveplayer : ExtractorApi() {
- override val name = "Gdrive"
- override val mainUrl = "https://gdriveplayer.to"
- override val requiresReferer = false
-
- private fun unpackJs(script: Element): String? {
- return script.select("script").find { it.data().contains("eval(function(p,a,c,k,e,d)") }
- ?.data()?.let { getAndUnpack(it) }
- }
-
- private fun String.decodeHex(): ByteArray {
- check(length % 2 == 0) { "Must have an even length" }
- return chunked(2)
- .map { it.toInt(16).toByte() }
- .toByteArray()
- }
-
- // https://stackoverflow.com/a/41434590/8166854
- private fun GenerateKeyAndIv(
- password: ByteArray,
- salt: ByteArray,
- hashAlgorithm: String = "MD5",
- keyLength: Int = 32,
- ivLength: Int = 16,
- iterations: Int = 1
- ): List? {
-
- val md = MessageDigest.getInstance(hashAlgorithm)
- val digestLength = md.digestLength
- val targetKeySize = keyLength + ivLength
- val requiredLength = (targetKeySize + digestLength - 1) / digestLength * digestLength
- val generatedData = ByteArray(requiredLength)
- var generatedLength = 0
-
- try {
- md.reset()
-
- while (generatedLength < targetKeySize) {
- if (generatedLength > 0)
- md.update(
- generatedData,
- generatedLength - digestLength,
- digestLength
- )
-
- md.update(password)
- md.update(salt, 0, 8)
- md.digest(generatedData, generatedLength, digestLength)
-
- for (i in 1 until iterations) {
- md.update(generatedData, generatedLength, digestLength)
- md.digest(generatedData, generatedLength, digestLength)
- }
-
- generatedLength += digestLength
- }
- return listOf(
- generatedData.copyOfRange(0, keyLength),
- generatedData.copyOfRange(keyLength, targetKeySize)
- )
- } catch (e: DigestException) {
- return null
- }
- }
-
- private fun cryptoAESHandler(
- data: AesData,
- pass: ByteArray,
- encrypt: Boolean = true
- ): String? {
- val (key, iv) = GenerateKeyAndIv(pass, data.s.decodeHex()) ?: return null
- val cipher = Cipher.getInstance("AES/CBC/NoPadding")
- return if (!encrypt) {
- cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))
- String(cipher.doFinal(base64DecodeArray(data.ct)))
- } else {
- cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))
- base64Encode(cipher.doFinal(data.ct.toByteArray()))
-
- }
- }
-
- private fun Regex.first(str: String): String? {
- 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?,
- subtitleCallback: (SubtitleFile) -> Unit,
- callback: (ExtractorLink) -> Unit
- ) {
- val document = app.get(url).document
-
- val eval = unpackJs(document)?.replace("\\", "") ?: return
- val data = 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 sourceData = decryptedData?.substringAfter("sources:[")?.substringBefore("],")
- val subData = decryptedData?.substringAfter("tracks:[")?.substringBefore("],")
-
- Regex("\"file\":\"(\\S+?)\".*?res=(\\d+)").findAll(sourceData ?: return).map {
- it.groupValues[1] to it.groupValues[2]
- }.toList().distinctBy { it.second }.map { (link, quality) ->
- callback.invoke(
- ExtractorLink(
- source = this.name,
- name = this.name,
- url = "${httpsify(link)}&res=$quality",
- referer = mainUrl,
- quality = quality.toIntOrNull() ?: Qualities.Unknown.value,
- headers = mapOf("Range" to "bytes=0-")
- )
- )
- }
-
- subData?.addMarks("file")?.addMarks("kind")?.addMarks("label").let { dataSub ->
- tryParseJson>("[$dataSub]")?.map { sub ->
- subtitleCallback.invoke(
- SubtitleFile(
- sub.label,
- httpsify(sub.file)
- )
- )
- }
- }
-
- }
-
- data class AesData(
- @JsonProperty("ct") val ct: String,
- @JsonProperty("iv") val iv: String,
- @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
deleted file mode 100644
index 9e5f5e74..00000000
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Mvidoo.kt
+++ /dev/null
@@ -1,47 +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.Qualities
-
-open class Mvidoo : ExtractorApi() {
- override val name = "Mvidoo"
- override val mainUrl = "https://mvidoo.com"
- override val requiresReferer = true
-
- private fun String.decodeHex(): String {
- require(length % 2 == 0) { "Must have an even length" }
- return String(
- chunked(2)
- .map { it.toInt(16).toByte() }
- .toByteArray()
- )
- }
-
- override suspend fun getUrl(
- url: String,
- referer: String?,
- subtitleCallback: (SubtitleFile) -> Unit,
- callback: (ExtractorLink) -> Unit
- ) {
- val document = app.get(url, referer = referer).text
- val data = Regex("""\{var\s*[^\s]+\s*=\s*(\[[^]]+])""").find(document)?.groupValues?.get(1)
- ?.removeSurrounding("[", "]")?.replace("\"", "")?.replace("\\x", "")?.split(",")?.map { it.decodeHex() }?.reversed()?.joinToString("") ?: return
- Regex("source\\s*src=\"([^\"]+)").find(data)?.groupValues?.get(1)?.let { link ->
- callback.invoke(
- ExtractorLink(
- this.name,
- this.name,
- link,
- "$mainUrl/",
- Qualities.Unknown.value,
- headers = mapOf(
- "Range" to "bytes=0-"
- )
- )
- )
- }
- }
-}
\ No newline at end of file
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
deleted file mode 100644
index 2b286abb..00000000
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/PlayLtXyz.kt
+++ /dev/null
@@ -1,79 +0,0 @@
-package com.lagradost.cloudstream3.extractors
-
-import android.util.Log
-import com.fasterxml.jackson.annotation.JsonProperty
-import com.lagradost.cloudstream3.app
-import com.lagradost.cloudstream3.utils.*
-import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
-
-open class PlayLtXyz: ExtractorApi() {
- override val name: String = "PlayLt"
- override val mainUrl: String = "https://play.playlt.xyz"
- override val requiresReferer = true
-
- private data class ResponseData(
- @JsonProperty("data") val data: String? = null
- )
-
- override suspend fun getUrl(url: String, referer: String?): List {
- val extractedLinksList = mutableListOf()
- //Log.i(this.name, "Result => (url) $url")
- var idUser = ""
- var idFile = ""
- var bodyText = ""
- val doc = app.get(url, referer = referer).document
- //Log.i(this.name, "Result => (url, script) $url / ${doc.select("script")}")
- bodyText = doc.select("script").firstOrNull {
- val text = it?.toString() ?: ""
- text.contains("var idUser")
- }?.toString() ?: ""
- //Log.i(this.name, "Result => (bodyText) $bodyText")
- if (bodyText.isNotBlank()) {
- idUser = "(?<=var idUser = \")(.*)(?=\";)".toRegex().find(bodyText)
- ?.groupValues?.get(0) ?: ""
-
- idFile = "(?<=var idfile = \")(.*)(?=\";)".toRegex().find(bodyText)
- ?.groupValues?.get(0) ?: ""
- }
- //Log.i(this.name, "Result => (idUser, idFile) $idUser / $idFile")
- if (idUser.isNotBlank() && idFile.isNotBlank()) {
- //val sess = HttpSession()
- val ajaxHead = mapOf(
- Pair("Origin", mainUrl),
- Pair("Referer", mainUrl),
- Pair("Sec-Fetch-Site", "same-site"),
- Pair("Sec-Fetch-Mode", "cors"),
- Pair("Sec-Fetch-Dest", "empty")
- )
- val ajaxData = mapOf(
- Pair("referrer", referer ?: mainUrl),
- Pair("typeend", "html")
- )
-
- //idUser = 608f7c85cf0743547f1f1b4e
- val posturl = "https://api-plhq.playlt.xyz/apiv5/$idUser/$idFile"
- val data = app.post(posturl, headers = ajaxHead, data = ajaxData)
- //Log.i(this.name, "Result => (posturl) $posturl")
- if (data.isSuccessful) {
- val itemstr = data.text
- Log.i(this.name, "Result => (data) $itemstr")
- tryParseJson(itemstr)?.let { item ->
- val linkUrl = item.data ?: ""
- if (linkUrl.isNotBlank()) {
- extractedLinksList.add(
- ExtractorLink(
- source = name,
- name = name,
- url = linkUrl,
- referer = url,
- quality = Qualities.Unknown.value,
- isM3u8 = true
- )
- )
- }
- }
- }
- }
- return extractedLinksList
- }
-}
\ No newline at end of file
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..da3ef278 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamSB.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/StreamSB.kt
@@ -1,34 +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.parseJson
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"
-}
-
-class Sbflix : StreamSB() {
- override var mainUrl = "https://sbflix.xyz"
- override var name = "Sbflix"
-}
-
-class Vidgomunime : StreamSB() {
- override var mainUrl = "https://vidgomunime.xyz"
-}
-
-class Sbthe : StreamSB() {
- override var mainUrl = "https://sbthe.com"
-}
-
class Ssbstream : StreamSB() {
override var mainUrl = "https://ssbstream.net"
}
@@ -77,10 +55,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 +76,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,
@@ -122,42 +96,31 @@ open class StreamSB : ExtractorApi() {
@JsonProperty("status_code") val statusCode: Int,
)
- override suspend fun getUrl(
- url: String,
- referer: String?,
- subtitleCallback: (SubtitleFile) -> Unit,
- callback: (ExtractorLink) -> Unit
- ) {
- val regexID =
- Regex("(embed-[a-zA-Z0-9]{0,8}[a-zA-Z0-9_-]+|/e/[a-zA-Z0-9]{0,8}[a-zA-Z0-9_-]+)")
+ override suspend fun getUrl(url: String, referer: String?): List? {
+ val regexID = Regex("(embed-[a-zA-Z0-9]{0,8}[a-zA-Z0-9_-]+|\\/e\\/[a-zA-Z0-9]{0,8}[a-zA-Z0-9_-]+)")
val id = regexID.findAll(url).map {
- it.value.replace(Regex("(embed-|/e/)"), "")
+ 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 bytes = id.toByteArray()
+ val bytesToHex = bytesToHex(bytes)
+ val master = "$mainUrl/sources43/6d6144797752744a454267617c7c${bytesToHex.lowercase()}7c7c4e61755a56456f34385243727c7c73747265616d7362/6b4a33767968506e4e71374f7c7c343837323439333133333462353935333633373836643638376337633462333634663539343137373761333635313533333835333763376333393636363133393635366136323733343435323332376137633763373337343732363536313664373336327c7c504d754478413835306633797c7c73747265616d7362"
val headers = mapOf(
- "watchsb" to "sbstream",
- )
- val mapped = app.get(
- master.lowercase(),
- headers = headers,
- referer = url,
- ).parsedSafe()
- // val urlmain = mapped.streamData.file.substringBefore("/hls/")
- M3u8Helper.generateM3u8(
- name,
- mapped?.streamData?.file ?: return,
- url,
- headers = headers
- ).forEach(callback)
-
- mapped.streamData.subs?.map {sub ->
- subtitleCallback.invoke(
- SubtitleFile(
- sub.label.toString(),
- sub.file ?: return@map null,
- )
+ "watchsb" to "streamsb",
)
- }
+ val urltext = app.get(master,
+ headers = headers,
+ allowRedirects = false
+ ).text
+ val mapped = urltext.let { parseJson(it) }
+ val testurl = app.get(mapped.streamData.file, headers = headers).text
+ // val urlmain = mapped.streamData.file.substringBefore("/hls/")
+ if (urltext.contains("m3u8") && testurl.contains("EXTM3U"))
+ return M3u8Helper.generateM3u8(
+ name,
+ mapped.streamData.file,
+ url,
+ headers = headers
+ )
+ return 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
deleted file mode 100644
index e6bbfeba..00000000
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Streamplay.kt
+++ /dev/null
@@ -1,81 +0,0 @@
-package com.lagradost.cloudstream3.extractors
-
-import com.fasterxml.jackson.annotation.JsonProperty
-import com.lagradost.cloudstream3.APIHolder.getCaptchaToken
-import com.lagradost.cloudstream3.ErrorLoadingException
-import com.lagradost.cloudstream3.SubtitleFile
-import com.lagradost.cloudstream3.app
-import com.lagradost.cloudstream3.utils.*
-import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
-import java.net.URI
-
-open class Streamplay : ExtractorApi() {
- override val name = "Streamplay"
- override val mainUrl = "https://streamplay.to"
- override val requiresReferer = true
-
- override suspend fun getUrl(
- url: String,
- referer: String?,
- subtitleCallback: (SubtitleFile) -> Unit,
- callback: (ExtractorLink) -> Unit
- ) {
- val request = app.get(url, referer = referer)
- val redirectUrl = request.url
- val mainServer = URI(redirectUrl).let {
- "${it.scheme}://${it.host}"
- }
- val key = redirectUrl.substringAfter("embed-").substringBefore(".html")
- val token =
- request.document.select("script").find { it.data().contains("sitekey:") }?.data()
- ?.substringAfterLast("sitekey: '")?.substringBefore("',")?.let { captchaKey ->
- getCaptchaToken(
- redirectUrl,
- captchaKey,
- referer = "$mainServer/"
- )
- } ?: throw ErrorLoadingException("can't bypass captcha")
- app.post(
- "$mainServer/player-$key-488x286.html", data = mapOf(
- "op" to "embed",
- "token" to token
- ),
- referer = redirectUrl,
- headers = mapOf(
- "Accept" to "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
- "Content-Type" to "application/x-www-form-urlencoded"
- )
- ).document.select("script").find { script ->
- script.data().contains("eval(function(p,a,c,k,e,d)")
- }?.let {
- val data = getAndUnpack(it.data()).substringAfter("sources=[").substringBefore(",desc")
- .replace("file", "\"file\"")
- .replace("label", "\"label\"")
- tryParseJson>("[$data}]")?.map { res ->
- callback.invoke(
- ExtractorLink(
- this.name,
- this.name,
- res.file ?: return@map null,
- "$mainServer/",
- when (res.label) {
- "HD" -> Qualities.P720.value
- "SD" -> Qualities.P480.value
- else -> Qualities.Unknown.value
- },
- headers = mapOf(
- "Range" to "bytes=0-"
- )
- )
- )
- }
- }
-
- }
-
- data class Source(
- @JsonProperty("file") val file: String? = null,
- @JsonProperty("label") val label: String? = null,
- )
-
-}
\ No newline at end of file
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..79c657b6 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/UpstreamExtractor.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/UpstreamExtractor.kt
@@ -1,23 +1,19 @@
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
+import com.lagradost.cloudstream3.utils.Qualities
-open class UpstreamExtractor : ExtractorApi() {
- override val name: String = "Upstream"
+class UpstreamExtractor: ExtractorApi() {
+ override val name: String = "Upstream.to"
override val mainUrl: String = "https://upstream.to"
override val requiresReferer = true
- override suspend fun getUrl(
- url: String,
- referer: String?,
- subtitleCallback: (SubtitleFile) -> Unit,
- callback: (ExtractorLink) -> Unit
- ) {
+ override suspend fun getUrl(url: String, referer: String?): List {
+ // WIP: m3u8 link fetched but sometimes not playing
//Log.i(this.name, "Result => (no extractor) ${url}")
+ val sources: MutableList = mutableListOf()
val doc = app.get(url, referer = referer).text
if (doc.isNotBlank()) {
var reg = Regex("(?<=master)(.*)(?=hls)")
@@ -34,9 +30,7 @@ open class UpstreamExtractor : ExtractorApi() {
domName = "${part}.${domName}"
}
domName.trimEnd('.')
- } else {
- ""
- }
+ } else { "" }
}
false -> ""
}
@@ -48,13 +42,18 @@ open class UpstreamExtractor : ExtractorApi() {
result?.forEach {
val linkUrl = "https://${domain}/hls/${it}/master.m3u8"
- M3u8Helper.generateM3u8(
- this.name,
- linkUrl,
- "$mainUrl/",
- headers = mapOf("Origin" to mainUrl)
- ).forEach(callback)
+ sources.add(
+ ExtractorLink(
+ name = "Upstream m3u8",
+ source = this.name,
+ url = linkUrl,
+ quality = Qualities.Unknown.value,
+ referer = referer ?: linkUrl,
+ isM3u8 = true
+ )
+ )
}
}
+ return sources
}
}
\ No newline at end of file
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..7b087157 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/VidSrcExtractor.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/VidSrcExtractor.kt
@@ -1,11 +1,12 @@
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
-import java.net.URI
+import com.lagradost.cloudstream3.utils.ExtractorApi
+import com.lagradost.cloudstream3.utils.ExtractorLink
+import com.lagradost.cloudstream3.utils.M3u8Helper
+import com.lagradost.cloudstream3.utils.loadExtractor
class VidSrcExtractor2 : VidSrcExtractor() {
override val mainUrl = "https://vidsrc.me/embed"
@@ -26,25 +27,6 @@ open class VidSrcExtractor : ExtractorApi() {
override val mainUrl = "$absoluteUrl/embed"
override val requiresReferer = false
- companion object {
- /** Infinite function to validate the vidSrc pass */
- suspend fun validatePass(url: String) {
- val uri = URI(url)
- val host = uri.host
-
- // Basically turn https://tm3p.vidsrc.stream/ -> https://vidsrc.stream/
- val referer = host.split(".").let {
- val size = it.size
- "https://" + it.subList(maxOf(0, size - 2), size).joinToString(".") + "/"
- }
-
- while (true) {
- app.get(url, referer = referer)
- delay(60_000)
- }
- }
- }
-
override suspend fun getUrl(
url: String,
referer: String?,
@@ -58,10 +40,7 @@ open class VidSrcExtractor : ExtractorApi() {
val datahash = it.attr("data-hash")
if (datahash.isNotBlank()) {
val links = try {
- app.get(
- "$absoluteUrl/srcrcp/$datahash",
- referer = "https://rcp.vidsrc.me/"
- ).url
+ app.get("$absoluteUrl/src/$datahash", referer = "https://source.vidsrc.me/").url
} catch (e: Exception) {
""
}
@@ -69,28 +48,17 @@ 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 passRegex = Regex("""['"](.*set_pass[^"']*)""")
- val pass = passRegex.find(srcresponse)?.groupValues?.get(1)?.replace(
- Regex("""^//"""), "https://"
- )
-
- callback.invoke(
- ExtractorLink(
- this.name,
- this.name,
- srcm3u8,
- "https://vidsrc.stream/",
- Qualities.Unknown.value,
- extractorData = pass,
- isM3u8 = true
- )
- )
+ val srcm3u8 = m3u8Regex.find(srcresponse)?.value ?: return@apmap
+ 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..89f4ca67 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/XStreamCdn.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/extractors/XStreamCdn.kt
@@ -1,32 +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"
-}
-
-class Rasacintaku: XStreamCdn() {
- override val mainUrl: String = "https://rasa-cintaku-semakin-berantai.xyz"
-}
-
class LayarKaca: XStreamCdn() {
override val name: String = "LayarKaca-xxi"
override val mainUrl: String = "https://layarkacaxxi.icu"
@@ -70,67 +50,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
deleted file mode 100644
index 43c4eefb..00000000
--- a/app/src/main/java/com/lagradost/cloudstream3/extractors/Zorofile.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-package com.lagradost.cloudstream3.extractors
-
-import com.fasterxml.jackson.annotation.JsonProperty
-import com.lagradost.cloudstream3.APIHolder.getCaptchaToken
-import com.lagradost.cloudstream3.ErrorLoadingException
-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
-
-open class Zorofile : ExtractorApi() {
- override val name = "Zorofile"
- override val mainUrl = "https://zorofile.com"
- override val requiresReferer = true
-
- override suspend fun getUrl(
- url: String,
- referer: String?,
- subtitleCallback: (SubtitleFile) -> Unit,
- callback: (ExtractorLink) -> Unit
- ) {
- val id = url.split("?").first().split("/").last()
- val token = app.get(
- url,
- referer = referer
- ).document.select("button.g-recaptcha").attr("data-sitekey").let { captchaKey ->
- getCaptchaToken(
- url,
- captchaKey,
- referer = referer
- )
- } ?: throw ErrorLoadingException("can't bypass captcha")
-
- val data = app.post(
- "$mainUrl/dl",
- data = mapOf(
- "op" to "embed",
- "file_code" to id,
- "auto" to "1",
- "referer" to "$referer/",
- "g-recaptcha-response" to token
- ),
- referer = url,
- headers = mapOf(
- "Accept" to "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
- "Content-Type" to "application/x-www-form-urlencoded",
- "Origin" to mainUrl,
- "Sec-Fetch-Dest" to "iframe",
- "Sec-Fetch-Mode" to "navigate",
- "Sec-Fetch-Site" to "same-origin",
- "Sec-Fetch-User" to "?1",
- "Upgrade-Insecure-Requests" to "1",
- )
- ).document.select("script").find { it.data().contains("var holaplayer;") }?.data()
- ?.substringAfter("sources: [")?.substringBefore("],")?.replace("src", "\"src\"")
- ?.replace("type", "\"type\"")
-
- tryParseJson("$data")?.let { res ->
- return M3u8Helper.generateM3u8(
- name,
- res.src ?: return@let,
- "$mainUrl/",
- headers = mapOf(
- "Origin" to mainUrl,
- )
- ).forEach(callback)
- }
- }
-
- private data class Sources(
- @JsonProperty("src") val src: String? = null,
- @JsonProperty("type") val type: String? = null,
- )
-}
\ No newline at end of file
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..df585cda 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/mvvm/ArchComponentExt.kt
@@ -7,7 +7,6 @@ import com.bumptech.glide.load.HttpException
import com.lagradost.cloudstream3.BuildConfig
import com.lagradost.cloudstream3.ErrorLoadingException
import kotlinx.coroutines.*
-import java.io.InterruptedIOException
import java.net.SocketTimeoutException
import java.net.UnknownHostException
import javax.net.ssl.SSLHandshakeException
@@ -15,7 +14,6 @@ import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
const val DEBUG_EXCEPTION = "THIS IS A DEBUG EXCEPTION!"
-const val DEBUG_PRINT = "DEBUG PRINT"
class DebugException(message: String) : Exception("$DEBUG_EXCEPTION\n$message")
@@ -25,12 +23,6 @@ inline fun debugException(message: () -> String) {
}
}
-inline fun debugPrint(tag: String = DEBUG_PRINT, message: () -> String) {
- if (BuildConfig.DEBUG) {
- Log.d(tag, message.invoke())
- }
-}
-
inline fun debugWarning(message: () -> String) {
if (BuildConfig.DEBUG) {
logError(DebugException(message.invoke()))
@@ -53,10 +45,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 +109,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)
}
@@ -147,8 +127,8 @@ fun CoroutineScope.launchSafe(
val obj: suspend CoroutineScope.() -> Unit = {
try {
block()
- } catch (throwable: Throwable) {
- logError(throwable)
+ } catch (e: Exception) {
+ logError(e)
}
}
@@ -177,7 +157,7 @@ suspend fun safeApiCall(
}
safeFail(throwable)
}
- is SocketTimeoutException, is InterruptedIOException -> {
+ is SocketTimeoutException -> {
Resource.Failure(
true,
null,
@@ -212,7 +192,7 @@ suspend fun safeApiCall(
true,
null,
null,
- (throwable.message ?: "SSLHandshakeException") + "\nTry a VPN or DNS."
+ (throwable.message ?: "SSLHandshakeException") + "\nTry again later."
)
}
else -> safeFail(throwable)
diff --git a/app/src/main/java/com/lagradost/cloudstream3/network/AdvancedWebview.kt b/app/src/main/java/com/lagradost/cloudstream3/network/AdvancedWebview.kt
new file mode 100644
index 00000000..8b02625e
--- /dev/null
+++ b/app/src/main/java/com/lagradost/cloudstream3/network/AdvancedWebview.kt
@@ -0,0 +1,483 @@
+package com.lagradost.cloudstream3.network
+
+
+import android.app.Dialog
+import android.net.http.SslError
+import android.util.Log
+import android.view.View
+import android.webkit.*
+import android.widget.RelativeLayout
+import com.lagradost.cloudstream3.*
+import com.lagradost.cloudstream3.utils.Coroutines.main
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.runBlocking
+import okhttp3.Response
+import org.jsoup.Jsoup
+import org.jsoup.nodes.Document
+import java.net.URI
+
+
+enum class WebViewActions {
+ VISIT_ADDRESS,
+ WAIT_FOR_PAGE_LOAD,
+ WAIT_FOR_X_SECONDS,
+ WAIT_FOR_NETWORK_CALL,
+ WAIT_FOR_NETWORK_IDLE,
+ WAIT_FOR_ELEMENT,
+ WAIT_FOR_ELEMENT_GONE,
+ EXECUTE_JAVASCRIPT,
+ WAIT_FOR_ELEMENT_TO_BE_CLICKABLE,
+ CAPTURE_REQUESTS_THAT_MATCH_REGEX,
+// SEND_KEYS_TO_ELEMENT,
+ RETURN
+}
+
+data class WebViewAction(val actionType: WebViewActions, val parameter: Any = "", val callback: (AdvancedWebView) -> Unit = { })
+
+class AdvancedWebView private constructor(
+ val url: String,
+ val actions: ArrayList,
+ val referer: String?,
+ val method: String,
+ val callback: (AdvancedWebView) -> Unit = { },
+ val debug: Boolean = false
+) {
+ companion object {
+ const val TAG = "AdvancedWebViewTag"
+ }
+ val headers = mapOf()
+ var webView: WebView? = null
+ val remainingActions: ArrayList = actions
+ var currentHTML: String = ""
+
+ // Made this a getter, because `currentHTML` changes on the fly
+ val document: Document?
+ get() = try { Jsoup.parse(currentHTML) } catch (e: Exception) { null }
+
+ private val Instance = this
+
+ data class Builder(
+ var url: String = "",
+ var actions: ArrayList = arrayListOf(),
+ var referer: String? = null,
+ var method: String = "GET",
+ var debug: Boolean = false
+ ) {
+ fun visitAddress(url: String, cb: (AdvancedWebView) -> Unit = { }) = apply {
+ if (this.url != "") {
+ addAction(WebViewAction(WebViewActions.VISIT_ADDRESS, url, cb))
+ } else this.url = url
+ }
+ fun setReferer(referer: String) = apply { this.referer = referer }
+ fun setMethod(method: String) = apply { this.method = method }
+
+ private fun addAction(action: WebViewAction) = apply { this.actions.add(action) }
+
+ fun waitForElement(selector: String, cb: (AdvancedWebView) -> Unit = { }) = apply {
+ addAction(WebViewAction(WebViewActions.WAIT_FOR_ELEMENT, selector, cb))
+ }
+ fun waitForElementGone(selector: String, cb: (AdvancedWebView) -> Unit = { }) = apply {
+ addAction(WebViewAction(WebViewActions.WAIT_FOR_ELEMENT, selector, cb))
+ }
+ fun waitForElementToBeClickable(selector: String, cb: (AdvancedWebView) -> Unit = { }) = apply {
+ addAction(WebViewAction(WebViewActions.WAIT_FOR_ELEMENT_TO_BE_CLICKABLE, selector, cb))
+ }
+ fun waitForSeconds(seconds: Long, cb: (AdvancedWebView) -> Unit = { }) = apply {
+ addAction(WebViewAction(WebViewActions.WAIT_FOR_X_SECONDS, seconds, cb))
+ }
+ fun waitForPageLoad(cb: (AdvancedWebView) -> Unit = { }) = apply {
+ addAction(WebViewAction(WebViewActions.WAIT_FOR_PAGE_LOAD, "", cb))
+ }
+ fun waitForNetworkIdle(cb: (AdvancedWebView) -> Unit = { }) = apply {
+ addAction(WebViewAction(WebViewActions.WAIT_FOR_NETWORK_IDLE, "", cb))
+ }
+ fun waitForNetworkCall(targetResource: String, cb: (AdvancedWebView) -> Unit = { }) = apply {
+ addAction(WebViewAction(WebViewActions.WAIT_FOR_NETWORK_CALL, targetResource, cb))
+ }
+ fun executeJavaScript(code: String, cb: (AdvancedWebView) -> Unit = { }) = apply {
+ addAction(WebViewAction(WebViewActions.EXECUTE_JAVASCRIPT, code, cb))
+ }
+ fun captureReqsThatMatchRegex(regex: Regex, cb: (AdvancedWebView) -> Unit = { }) = apply {
+ addAction(WebViewAction(WebViewActions.CAPTURE_REQUESTS_THAT_MATCH_REGEX, regex, cb))
+ }
+// fun sendKeysToElement(selector: String, text: String, delayInMsPerKeyPress: Long = 50, cb: (AdvancedWebView) -> Unit = { }) = apply {
+// addAction(WebViewAction(WebViewActions.SEND_KEYS_TO_ELEMENT, "$selector(__++`__||__`++__)$text(__++`__||__`++__)$delayInMsPerKeyPress", cb))
+// }
+ fun debug() = apply { debug = true }
+ fun close() = apply { addAction(WebViewAction(WebViewActions.RETURN, "")) }
+
+ fun build(callback: (AdvancedWebView) -> Unit = { }) = AdvancedWebView(this.url, this.actions, this.referer, this.method, callback, debug)
+ fun buildAndStart(callback: (AdvancedWebView) -> Unit = { }) = build(callback).apply { this.start() }
+ }
+
+ private var actionExecutionsPaused = false
+ private var networkIdleTimestamp = -1;
+ private var pageHasLoaded = false;
+ private var isInSleep = false
+ private var isSendingKeys = false
+ private var actionStartTimestamp = -1;
+
+ private fun onActionEnded() {
+ actionExecutionsPaused = false
+ isSendingKeys = false
+ actionStartTimestamp = -1
+ }
+
+ var Error = ""
+
+ private suspend fun tryExecuteAction() {
+ if (actionExecutionsPaused || remainingActions.size == 0) return
+ actionExecutionsPaused = true
+ actionStartTimestamp = (System.currentTimeMillis() / 1000).toInt()
+
+ main {
+ if (remainingActions.size > 0) {
+ val action = remainingActions[0]
+ when (action.actionType){
+ WebViewActions.WAIT_FOR_ELEMENT -> {
+ webView?.evaluateJavascript("document.querySelector(\"${action.parameter}\")") {
+ Log.i(TAG, "WAIT_FOR_ELEMENT:: <$it>")
+ if (it == "{}") {
+ updateCurrentHtmlAndRun(action.callback)
+ remainingActions.remove(action)
+ }
+ onActionEnded()
+ }
+ }
+
+ WebViewActions.VISIT_ADDRESS -> {
+ webView?.loadUrl(action.parameter as String)
+
+ updateCurrentHtmlAndRun(action.callback)
+ remainingActions.remove(action)
+ onActionEnded()
+ }
+
+// WebViewActions.SEND_KEYS_TO_ELEMENT -> {
+// isSendingKeys = true
+// val (element, characters, timing) = (action.parameter as String).split("(__++`__||__`++__)") // discriminator
+// val msPerKey: Long = timing.toLongOrNull() ?: return@main
+//
+// Log.i(TAG, "SEND_KEYS_TO_ELEMENT:: start")
+// webView?.evaluateJavascript("document.querySelector(`$element`)?.click()") {
+// main {
+// delay(300)
+// for (character in characters) {
+// Log.i(TAG, "SEND_KEYS_TO_ELEMENT:: character :: $character")
+//
+// webView?.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, character.code))
+// delay(70)
+// webView?.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_UP, character.code))
+// delay(msPerKey)
+// }
+//
+// updateCurrentHtmlAndRun(action.callback)
+// remainingActions.remove(action)
+// onActionEnded()
+// }
+// }
+// }
+
+ WebViewActions.WAIT_FOR_ELEMENT_TO_BE_CLICKABLE -> {
+ webView?.evaluateJavascript(
+ """
+ ((selector) => {
+ const elem = document.querySelector(selector)
+ if (elem == undefined) return
+ const attribute = elem.getAttribute("disabled")
+ if (attribute === "true" || attribute === '') return
+
+ return "" + (!elem.disabled || true)
+ })(`${action.parameter}`);
+ """.trimIndent()) {
+ if (it == "\"true\""){
+ updateCurrentHtmlAndRun(action.callback)
+ remainingActions.remove(action)
+ }
+ onActionEnded()
+ }
+ }
+
+ WebViewActions.WAIT_FOR_ELEMENT_GONE -> {
+ webView?.evaluateJavascript("\"\"+ (document.querySelector(\"${action.parameter}\") == undefined)") {
+ if (it == "\"true\"") {
+ updateCurrentHtmlAndRun(action.callback)
+ remainingActions.remove(action)
+ }
+ onActionEnded()
+ }
+ }
+
+ WebViewActions.WAIT_FOR_NETWORK_IDLE -> {
+// if (!pageHasLoaded || ((System.currentTimeMillis() / 1000L) - networkIdleTimestamp) < 10) return@main
+ // we need at least 10 seconds of no network calls being done in order to be in an "IDLE" state
+
+ updateCurrentHtmlAndRun(action.callback)
+ remainingActions.remove(action)
+
+ onActionEnded()
+ }
+
+ WebViewActions.WAIT_FOR_X_SECONDS -> {
+ Log.i(TAG, "Waiting for ${remainingActions[0].parameter} seconds...")
+ isInSleep = true
+ delay(action.parameter as Long * 1000)
+ isInSleep = false
+ Log.i(TAG, "Finished waiting!")
+ updateCurrentHtmlAndRun(action.callback)
+ remainingActions.remove(action)
+
+ onActionEnded()
+ }
+
+ WebViewActions.EXECUTE_JAVASCRIPT -> {
+ Log.i(TAG, "Executing javascript from action...")
+ webView?.evaluateJavascript(action.parameter as String) {
+ Log.i(TAG, "JavaScript Execution done! Result: <$it>")
+ updateCurrentHtmlAndRun(action.callback)
+ remainingActions.remove(action)
+
+ onActionEnded()
+ }
+ }
+
+ WebViewActions.RETURN -> {
+ updateCurrentHtmlAndRun() { /* Do nothing, we only want to update the html */ }
+ destroyWebView()
+ remainingActions.clear()
+ actionStartTimestamp = -1
+ }
+
+ else -> {
+ onActionEnded()
+ }
+ }
+ }
+ }
+ }
+
+ private fun destroyWebView() {
+ main {
+ webView?.stopLoading()
+ webView?.destroy()
+ webView = null
+ Log.i(TAG, "Destroyed the WebView!")
+ }
+ }
+
+ private fun updateCurrentHtmlAndRun(cb: (AdvancedWebView) -> Unit) {
+ if (webView == null) {
+ Instance.run(cb)
+ return
+ }
+
+ main {
+ webView?.evaluateJavascript("document.documentElement.outerHTML") {
+ currentHTML = it
+ .replace("\\u003C", "<")
+ .replace("\\\"", "\"")
+ .replace("\\n", "\n")
+ .replace("\\t", "\t")
+ .trimStart('"').trimEnd('"')
+
+ Instance.run(cb)
+ }
+ }
+ }
+
+ var initialized = false
+
+ suspend fun waitUntilDone() = apply {
+ while (!initialized) {
+ delay(100)
+ }
+ while (webView != null) {
+ delay(100)
+ }
+ }
+
+ val capturedRequests = arrayListOf()
+
+ private var dialog: Dialog? = null
+
+ fun start() {
+ main {
+ try {
+ webView = WebView(
+ AcraApplication.context
+ ?: throw RuntimeException("No base context in WebViewResolver")
+ ).apply {
+ // Bare minimum to bypass captcha
+ settings.javaScriptEnabled = true
+ settings.domStorageEnabled = true
+ settings.userAgentString = USER_AGENT
+ settings.blockNetworkImage = true
+ }
+ } catch (e: Exception) {
+ Error = "Error: Failed to create an Advanced WebView, reason: <${e.message}>"
+ Log.e(TAG, Error)
+ Log.e(TAG, e.toString())
+ destroyWebView()
+ callback(this)
+ }
+
+ if (debug) {
+ webView!!.visibility = View.VISIBLE
+ val layout = RelativeLayout.LayoutParams(
+ RelativeLayout.LayoutParams.MATCH_PARENT,
+ RelativeLayout.LayoutParams.MATCH_PARENT
+ )
+ dialog = Dialog(MainActivity.context!!, android.R.style.Theme_Black_NoTitleBar_Fullscreen)
+ dialog!!.addContentView(webView as View, layout)
+ dialog!!.show()
+ }
+
+ try {
+ webView?.webViewClient = object : WebViewClient() {
+ override fun onPageFinished(view: WebView?, url: String?) {
+ super.onPageFinished(view, url)
+ pageHasLoaded = true
+ networkIdleTimestamp = (System.currentTimeMillis() / 1000).toInt();
+
+ if (remainingActions.size > 0 && remainingActions[0].actionType == WebViewActions.WAIT_FOR_PAGE_LOAD) {
+ Log.i(TAG, "PAGE FINISHED!")
+ val action = remainingActions[0]
+ updateCurrentHtmlAndRun(action.callback)
+ remainingActions.remove(action)
+ }
+ }
+
+ override fun onLoadResource(view: WebView?, url: String?) {
+ super.onLoadResource(view, url)
+ networkIdleTimestamp = (System.currentTimeMillis() / 1000L).toInt();
+ if (remainingActions.size > 0) {
+ val action = remainingActions[0]
+ when (action.actionType) {
+ WebViewActions.WAIT_FOR_NETWORK_CALL -> {
+ if (URI(url) == URI(action.parameter as String)) {
+ updateCurrentHtmlAndRun(action.callback)
+ remainingActions.remove(action)
+ }
+ }
+ else -> { /* nothing */ }
+ }
+ }
+ }
+
+ override fun shouldInterceptRequest(
+ view: WebView,
+ request: WebResourceRequest
+ ): WebResourceResponse? = runBlocking {
+ networkIdleTimestamp = (System.currentTimeMillis() / 1000L).toInt();
+ val webViewUrl = request.url.toString()
+
+ val blacklistedFiles = listOf(
+ ".jpg", ".png", ".webp", ".mpg",
+ ".mpeg", ".jpeg", ".webm", ".mp4",
+ ".mp3", ".gifv", ".flv", ".asf",
+ ".mov", ".mng", ".mkv", ".ogg",
+ ".avi", ".wav", ".woff2", ".woff",
+ ".ttf", ".vtt", ".srt",
+ ".ts", ".gif",
+ // Warning, this might fuck some future sites, but it's used to make Sflix work.
+ "wss://"
+ )
+
+ val response = try {
+ when {
+ blacklistedFiles.any { URI(webViewUrl).path.contains(it) } || webViewUrl.endsWith(
+ "/favicon.ico"
+ ) -> WebResourceResponse(
+ "image/png",
+ null,
+ null
+ )
+
+ request.method == "GET" -> app.get(
+ webViewUrl,
+ headers = request.requestHeaders
+ ).okhttpResponse.toWebResourceResponse()
+
+ request.method == "POST" -> app.post(
+ webViewUrl,
+ headers = request.requestHeaders
+ ).okhttpResponse.toWebResourceResponse()
+
+ else -> return@runBlocking super.shouldInterceptRequest(
+ view,
+ request
+ )
+ }
+ } catch (e: Exception) {
+ null
+ }
+
+ if (remainingActions.size > 0){
+ val action = remainingActions[0]
+
+ when (action.actionType) {
+ WebViewActions.CAPTURE_REQUESTS_THAT_MATCH_REGEX -> {
+ if ((action.parameter as Regex).containsMatchIn(webViewUrl)) {
+ if (response != null) capturedRequests.add(response)
+ updateCurrentHtmlAndRun(action.callback)
+ remainingActions.remove(action)
+ }
+ }
+ else -> { /* nothing */ }
+ }
+ }
+
+ return@runBlocking response
+ }
+
+ override fun onReceivedSslError(
+ view: WebView?,
+ handler: SslErrorHandler?,
+ error: SslError?
+ ) {
+ handler?.proceed() // Ignore ssl issues
+ }
+ }
+ webView?.loadUrl(url, headers.toMap())
+ } catch (e: Exception){
+ Error = "Failed to create a WebView client!"
+ Log.e(TAG, Error)
+ destroyWebView()
+ Instance.run(callback)
+ return@main
+ }
+ initialized = true
+
+ while (remainingActions.size > 0 && webView != null) {
+ if (!isInSleep && !isSendingKeys && actionStartTimestamp != -1 && ((System.currentTimeMillis()/1000) - actionStartTimestamp > 20)) {
+ Log.e(TAG, "AdvancedWebview:: Timeout, an action failed to end in under 20 seconds...")
+ Error = "ActionTimeout"
+ break
+ }
+
+ delay(300)
+ if (!actionExecutionsPaused) tryExecuteAction()
+ }
+ try {
+ updateCurrentHtmlAndRun(callback)
+ } catch (e: Exception) {
+ Log.e(TAG, "Err: $e")
+ }
+ if (debug) dialog!!.hide()
+ destroyWebView()
+ }
+ }
+ fun Response.toWebResourceResponse(): WebResourceResponse {
+ val contentTypeValue = this.header("Content-Type")
+ // 1. contentType. 2. charset
+ val typeRegex = Regex("""(.*);(?:.*charset=(.*)(?:|;)|)""")
+ return if (contentTypeValue != null) {
+ val found = typeRegex.find(contentTypeValue)
+ val contentType = found?.groupValues?.getOrNull(1)?.ifBlank { null } ?: contentTypeValue
+ val charset = found?.groupValues?.getOrNull(2)?.ifBlank { null }
+ WebResourceResponse(contentType, charset, this.body?.byteStream())
+ } else {
+ WebResourceResponse("application/octet-stream", null, this.body?.byteStream())
+ }
+ }
+}
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..05836269 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/network/CloudflareKiller.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/network/CloudflareKiller.kt
@@ -5,12 +5,10 @@ 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
import okhttp3.*
-import java.net.URI
@AnyThread
@@ -25,27 +23,8 @@ 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)
- }
- }
-
val savedCookies: MutableMap> = mutableMapOf()
- /**
- * Gets the headers with cookies, webview user agent included!
- * */
- fun getCookieHeaders(url: String): Headers {
- val userAgentHeaders = WebViewResolver.webViewUserAgent?.let {
- mapOf("user-agent" to it)
- } ?: emptyMap()
-
- return getHeaders(userAgentHeaders, savedCookies[URI(url).host] ?: emptyMap())
- }
-
override fun intercept(chain: Interceptor.Chain): Response = runBlocking {
val request = chain.request()
val cookies = savedCookies[request.url.host]
@@ -64,9 +43,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/DohProviders.kt b/app/src/main/java/com/lagradost/cloudstream3/network/DohProviders.kt
index 55e09251..5372d0be 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/network/DohProviders.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/network/DohProviders.kt
@@ -64,24 +64,4 @@ fun OkHttpClient.Builder.addAdGuardDns() = (
"94.140.14.140",
"94.140.14.141",
)
- ))
-
-fun OkHttpClient.Builder.addDNSWatchDns() = (
- addGenericDns(
- "https://resolver2.dns.watch/dns-query",
- // https://dns.watch/
- listOf(
- "84.200.69.80",
- "84.200.70.40",
- )
- ))
-
-fun OkHttpClient.Builder.addQuad9Dns() = (
- addGenericDns(
- "https://dns.quad9.net/dns-query",
- // https://www.quad9.net/service/service-addresses-and-features
- listOf(
- "9.9.9.9",
- "149.112.112.112",
- )
- ))
\ No newline at end of file
+ ))
\ 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..85e9d318 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,19 @@ 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.getCookies
import com.lagradost.nicehttp.ignoreAllSSLErrors
import okhttp3.Cache
import okhttp3.Headers
import okhttp3.Headers.Companion.toHeaders
import okhttp3.OkHttpClient
-import org.conscrypt.Conscrypt
+import okhttp3.Request
import java.io.File
-import java.security.Security
+import java.util.concurrent.TimeUnit
+
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()
@@ -36,8 +36,6 @@ fun Requests.initClient(context: Context): OkHttpClient {
2 -> addCloudFlareDns()
// 3 -> addOpenDns()
4 -> addAdGuardDns()
- 5 -> addDNSWatchDns()
- 6 -> addQuad9Dns()
}
}
// Needs to be build as otherwise the other builders will change this object
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..5583d34b 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,41 +137,22 @@ 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
// Less data, low chance of causing issues.
// blockNetworkImage also does this job but i will keep it for the future.
- val blacklistedFiles = listOf(
- ".jpg",
- ".png",
- ".webp",
- ".mpg",
- ".mpeg",
- ".jpeg",
- ".webm",
- ".mp4",
- ".mp3",
- ".gifv",
- ".flv",
- ".asf",
- ".mov",
- ".mng",
- ".mkv",
- ".ogg",
- ".avi",
- ".wav",
- ".woff2",
- ".woff",
- ".ttf",
- ".css",
- ".vtt",
- ".srt",
- ".ts",
- ".gif",
+ val blacklistedFiles = listOf(
+ ".jpg", ".png", ".webp", ".mpg",
+ ".mpeg", ".jpeg", ".webm", ".mp4",
+ ".mp3", ".gifv", ".flv", ".asf",
+ ".mov", ".mng", ".mkv", ".ogg",
+ ".avi", ".wav", ".woff2", ".woff",
+ ".ttf", ".vtt", ".srt",
+ ".ts", ".gif",
// Warning, this might fuck some future sites, but it's used to make Sflix work.
"wss://"
)
@@ -259,19 +231,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/PluginManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt
index 0dee57eb..93bc85bf 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,30 @@
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 androidx.core.app.NotificationCompat
-import androidx.core.app.NotificationManagerCompat
-import androidx.fragment.app.FragmentActivity
+import android.app.Activity
+import android.util.Log
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.CommonActivity.showToast
-import com.lagradost.cloudstream3.MainAPI.Companion.settingsForProvider
-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.CommonActivity.showToast
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.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.APIHolder.removePluginMapping
+import com.lagradost.cloudstream3.mvvm.logError
+import com.lagradost.cloudstream3.plugins.RepositoryManager.PREBUILT_REPOSITORIES
+import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.extractorApis
-import dalvik.system.PathClassLoader
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.io.File
@@ -50,9 +35,6 @@ import java.util.*
const val PLUGINS_KEY = "PLUGINS_KEY"
const val PLUGINS_KEY_LOCAL = "PLUGINS_KEY_LOCAL"
-const val EXTENSIONS_CHANNEL_ID = "cloudstream3.extensions"
-const val EXTENSIONS_CHANNEL_NAME = "Extensions"
-const val EXTENSIONS_CHANNEL_DESCRIPT = "Extension notification channel"
// Data class for internal storage
data class PluginData(
@@ -93,8 +75,6 @@ object PluginManager {
const val TAG = "PluginManager"
- private var hasCreatedNotChanel = false
-
/**
* Store data about the plugin for fetching later
* */
@@ -129,10 +109,6 @@ object PluginManager {
val plugins = getPluginsOnline().filter {
!it.filePath.contains(repositoryPath)
}
- val file = File(repositoryPath)
- normalSafeApiCall {
- if (file.exists()) file.deleteRecursively()
- }
setKey(PLUGINS_KEY, plugins)
}
}
@@ -145,15 +121,11 @@ 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"
-
- public var currentlyLoading: String? = null
+ private val LOCAL_PLUGINS_PATH =
+ Environment.getExternalStorageDirectory().absolutePath + "/Cloudstream3/plugins"
// Maps filepath to plugin
- val plugins: MutableMap =
+ private val plugins: MutableMap =
LinkedHashMap()
// Maps urls to plugin
@@ -166,11 +138,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)
)
@@ -186,31 +158,18 @@ object PluginManager {
val onlineData: Pair,
) {
val isOutdated =
- onlineData.second.version > savedData.version || onlineData.second.version == PLUGIN_VERSION_ALWAYS_UPDATE
+ onlineData.second.version != savedData.version || onlineData.second.version == PLUGIN_VERSION_ALWAYS_UPDATE
val isDisabled = onlineData.second.status == PROVIDER_STATUS_DOWN
-
- fun validOnlineData(context: Context): Boolean {
- return getPluginPath(
- context,
- savedData.internalName,
- onlineData.first
- ).absolutePath == savedData.filePath
- }
}
- // var allCurrentOutDatedPlugins: Set = emptySet()
+ var allCurrentOutDatedPlugins: Set = emptySet()
- suspend fun loadSinglePlugin(context: Context, 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
- }
- ?: getPluginsLocal().firstOrNull {
- it.internalName.replace("provider", "", ignoreCase = true) == apiName
- })?.let { savedData ->
+ suspend fun loadSinglePlugin(activity: Activity, apiName: String): Boolean {
+ return (getPluginsOnline().firstOrNull { it.internalName == apiName }
+ ?: getPluginsLocal().firstOrNull { it.internalName == apiName })?.let { savedData ->
// OnlinePluginData(savedData, onlineData)
loadPlugin(
- context,
+ activity,
File(savedData.filePath),
savedData
)
@@ -225,10 +184,6 @@ object PluginManager {
* 4. Else load the plugin normally
**/
fun updateAllOnlinePluginsAndLoadThem(activity: Activity) {
- // Load all plugins as fast as possible!
- loadAllOnlinePlugins(activity)
- afterPluginsLoadedEvent.invoke(false)
-
val urls = (getKey>(REPOSITORIES_KEY)
?: emptyArray()) + PREBUILT_REPOSITORIES
@@ -238,168 +193,48 @@ object PluginManager {
// Iterates over all offline plugins, compares to remote repo and returns the plugins which are outdated
val outdatedPlugins = getPluginsOnline().map { savedData ->
- onlinePlugins
- .filter { onlineData -> savedData.internalName == onlineData.second.internalName }
+ onlinePlugins.filter { onlineData -> savedData.internalName == onlineData.second.internalName }
.map { onlineData ->
OnlinePluginData(savedData, onlineData)
- }.filter {
- it.validOnlineData(activity)
}
}.flatten().distinctBy { it.onlineData.second.url }
+ allCurrentOutDatedPlugins = outdatedPlugins.toSet()
- debugPrint {
- "Outdated plugins: ${outdatedPlugins.filter { it.isOutdated }}"
- }
+ Log.i(TAG, "Outdated plugins: ${outdatedPlugins.filter { it.isOutdated }}")
- val updatedPlugins = mutableListOf()
-
- outdatedPlugins.apmap { pluginData ->
- if (pluginData.isDisabled) {
- //updatedPlugins.add(activity.getString(R.string.single_plugin_disabled, pluginData.onlineData.second.name))
- unloadPlugin(pluginData.savedData.filePath)
- } else if (pluginData.isOutdated) {
- downloadPlugin(
+ outdatedPlugins.apmap {
+ if (it.isDisabled) {
+ return@apmap
+ } else if (it.isOutdated) {
+ downloadAndLoadPlugin(
activity,
- pluginData.onlineData.second.url,
- pluginData.savedData.internalName,
- File(pluginData.savedData.filePath),
- true
- ).let { success ->
- if (success)
- updatedPlugins.add(pluginData.onlineData.second.name)
- }
+ it.onlineData.second.url,
+ it.savedData.internalName,
+ it.onlineData.first
+ )
+ } else {
+ loadPlugin(
+ activity,
+ File(it.savedData.filePath),
+ it.savedData
+ )
}
}
- main {
- val uitext = txt(R.string.plugins_updated, updatedPlugins.size)
- createNotification(activity, uitext, updatedPlugins)
- }
-
- // ioSafe {
- afterPluginsLoadedEvent.invoke(false)
- // }
-
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) {
- // Load all plugins as fast as possible!
- (getPluginsOnline()).toList().apmap { pluginData ->
- loadPlugin(
- context,
- File(pluginData.filePath),
- pluginData
- )
- }
+ fun loadAllOnlinePlugins(activity: Activity) {
+ File(activity.filesDir, ONLINE_PLUGINS_FOLDER).listFiles()?.sortedBy { it.name }
+ ?.apmap { file ->
+ maybeLoadPlugin(activity, file)
+ }
}
- /**
- * 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 +252,22 @@ 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
}
/**
* @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,30 +311,30 @@ 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)
+ if (data.url != null) { // TODO: make this cleaner
+ urlPlugins[data.url] = pluginInstance
+ }
+ 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
false
}
}
- fun unloadPlugin(absolutePath: String) {
+ private fun unloadPlugin(absolutePath: String) {
Log.i(TAG, "Unloading plugin: $absolutePath")
val plugin = plugins[absolutePath]
if (plugin == null) {
@@ -540,7 +358,6 @@ object PluginManager {
classLoaders.values.removeIf { v -> v == plugin }
plugins.remove(absolutePath)
- urlPlugins.values.removeIf { v -> v == plugin }
}
/**
@@ -554,75 +371,41 @@ object PluginManager {
) + "." + name.hashCode()
}
- /**
- * This should not be changed as it is used to also detect if a plugin is installed!
- **/
- fun getPluginPath(
- context: Context,
+ suspend fun downloadAndLoadPlugin(
+ activity: Activity,
+ pluginUrl: String,
internalName: String,
repositoryUrl: String
- ): File {
- val folderName = getPluginSanitizedFileName(repositoryUrl) // Guaranteed unique
- val fileName = getPluginSanitizedFileName(internalName)
- return File("${context.filesDir}/${ONLINE_PLUGINS_FOLDER}/${folderName}/$fileName.cs3")
- }
-
- suspend fun downloadPlugin(
- activity: Activity,
- pluginUrl: String,
- internalName: String,
- repositoryUrl: String,
- loadPlugin: Boolean
- ): Boolean {
- val file = getPluginPath(activity, internalName, repositoryUrl)
- return downloadPlugin(activity, pluginUrl, internalName, file, loadPlugin)
- }
-
- suspend fun downloadPlugin(
- activity: Activity,
- pluginUrl: String,
- internalName: String,
- file: File,
- loadPlugin: Boolean
): Boolean {
try {
- Log.d(TAG, "Downloading plugin: $pluginUrl to ${file.absolutePath}")
+ val folderName = getPluginSanitizedFileName(repositoryUrl) // Guaranteed unique
+ val fileName = getPluginSanitizedFileName(internalName)
+ Log.i(TAG, "Downloading plugin: $pluginUrl to $folderName/$fileName")
// 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
+ val file = downloadPluginToFile(activity, pluginUrl, fileName, folderName)
+ return loadPlugin(
+ activity,
+ file ?: return false,
+ PluginData(internalName, pluginUrl, true, file.absolutePath, PLUGIN_VERSION_NOT_SET)
)
-
- return if (loadPlugin) {
- unloadPlugin(file.absolutePath)
- loadPlugin(
- activity,
- newFile,
- data
- )
- } else {
- setPluginData(data)
- true
- }
} catch (e: Exception) {
logError(e)
return false
}
}
- suspend fun deletePlugin(file: File): Boolean {
- val list =
- (getPluginsLocal() + getPluginsOnline()).filter { it.filePath == file.absolutePath }
+ /**
+ * @param isFilePath will treat the pluginUrl as as the filepath instead of url
+ * */
+ suspend fun deletePlugin(pluginIdentifier: String, isFilePath: Boolean): Boolean {
+ val data =
+ (if (isFilePath) (getPluginsLocal() + getPluginsOnline()).firstOrNull { it.filePath == pluginIdentifier }
+ else getPluginsOnline().firstOrNull { it.url == pluginIdentifier }) ?: return false
return try {
- if (File(file.absolutePath).delete()) {
- unloadPlugin(file.absolutePath)
- list.forEach { deletePluginData(it) }
+ if (File(data.filePath).delete()) {
+ unloadPlugin(data.filePath)
+ deletePluginData(data)
return true
}
false
@@ -630,66 +413,4 @@ object PluginManager {
false
}
}
-
- private fun Context.createNotificationChannel() {
- hasCreatedNotChanel = true
- // Create the NotificationChannel, but only on API 26+ because
- // the NotificationChannel class is new and not in the support library
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- val name = EXTENSIONS_CHANNEL_NAME //getString(R.string.channel_name)
- val descriptionText =
- EXTENSIONS_CHANNEL_DESCRIPT//getString(R.string.channel_description)
- val importance = NotificationManager.IMPORTANCE_LOW
- val channel = NotificationChannel(EXTENSIONS_CHANNEL_ID, name, importance).apply {
- description = descriptionText
- }
- // Register the channel with the system
- val notificationManager: NotificationManager =
- this.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
- notificationManager.createNotificationChannel(channel)
- }
- }
-
- private fun createNotification(
- context: Context,
- uitext: UiText,
- extensions: List
- ): Notification? {
- try {
-
- if (extensions.isEmpty()) return null
-
- val content = extensions.joinToString(", ")
-// main { // DON'T WANT TO SLOW IT DOWN
- val builder = NotificationCompat.Builder(context, EXTENSIONS_CHANNEL_ID)
- .setAutoCancel(false)
- .setColorized(true)
- .setOnlyAlertOnce(true)
- .setSilent(true)
- .setPriority(NotificationCompat.PRIORITY_LOW)
- .setColor(context.colorFromAttribute(R.attr.colorPrimary))
- .setContentTitle(uitext.asString(context))
- //.setContentTitle(context.getString(title, extensionNames.size))
- .setSmallIcon(R.drawable.ic_baseline_extension_24)
- .setStyle(
- NotificationCompat.BigTextStyle()
- .bigText(content)
- )
- .setContentText(content)
-
- if (!hasCreatedNotChanel) {
- context.createNotificationChannel()
- }
-
- val notification = builder.build()
- with(NotificationManagerCompat.from(context)) {
- // notificationId is a unique int for each notification that you must define
- notify((System.currentTimeMillis() / 1000).toInt(), notification)
- }
- return notification
- } catch (e: Exception) {
- logError(e)
- return null
- }
- }
}
\ No newline at end of file
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..c7e0ff86 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,59 +69,23 @@ 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()
- } catch (t: Throwable) {
- logError(t)
+ } catch (e : Exception) {
+ logError(e)
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
}
@@ -143,21 +103,29 @@ object RepositoryManager {
}
suspend fun downloadPluginToFile(
+ context: Context,
pluginUrl: String,
- file: File
+ fileName: String,
+ folder: String
): File? {
return suspendSafeApiCall {
- file.mkdirs()
+ 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 (file.exists()) {
- file.delete()
+ if (newFile.exists()) {
+ newFile.delete()
}
- file.createNewFile()
+ newFile.createNewFile()
- val body = app.get(convertRawGitUrl(pluginUrl)).okhttpResponse.body
- write(body.byteStream(), file.outputStream())
- file
+ val body = app.get(pluginUrl).okhttpResponse.body
+ write(body.byteStream(), newFile.outputStream())
+ newFile
}
}
@@ -192,17 +160,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 +173,4 @@ object RepositoryManager {
output.write(dataBuffer, 0, readBytes)
}
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/VotingApi.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/VotingApi.kt
deleted file mode 100644
index f099ad1a..00000000
--- a/app/src/main/java/com/lagradost/cloudstream3/plugins/VotingApi.kt
+++ /dev/null
@@ -1,126 +0,0 @@
-package com.lagradost.cloudstream3.plugins
-
-import android.util.Log
-import android.widget.Toast
-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 java.security.MessageDigest
-import com.lagradost.cloudstream3.app
-import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
-import com.lagradost.cloudstream3.utils.Coroutines.main
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.sync.Mutex
-import kotlinx.coroutines.sync.withLock
-
-object VotingApi { // please do not cheat the votes lol
- private const val LOGKEY = "VotingApi"
-
- enum class VoteType(val value: Int) {
- UPVOTE(1),
- DOWNVOTE(-1),
- NONE(0)
- }
-
- private val apiDomain = "https://api.countapi.xyz"
-
- private fun transformUrl(url: String): String = // dont touch or all votes get reset
- MessageDigest
- .getInstance("SHA-256")
- .digest("${url}#funny-salt".toByteArray())
- .fold("") { str, it -> str + "%02x".format(it) }
-
- suspend fun SitePlugin.getVotes(): Int {
- return getVotes(url)
- }
-
- suspend fun SitePlugin.vote(requestType: VoteType): Int {
- return vote(url, requestType)
- }
-
- fun SitePlugin.getVoteType(): VoteType {
- return getVoteType(url)
- }
-
- fun SitePlugin.canVote(): Boolean {
- return canVote(this.url)
- }
-
- // Plugin url to Int
- private val votesCache = mutableMapOf()
-
- suspend fun getVotes(pluginUrl: String): Int {
- val url = "${apiDomain}/get/cs3-votes/${transformUrl(pluginUrl)}"
- Log.d(LOGKEY, "Requesting: $url")
- return votesCache[pluginUrl] ?: app.get(url).parsedSafe()?.value?.also {
- votesCache[pluginUrl] = it
- } ?: (0.also {
- ioSafe {
- createBucket(pluginUrl)
- }
- })
- }
-
- fun getVoteType(pluginUrl: String): VoteType {
- return getKey("cs3-votes/${transformUrl(pluginUrl)}") ?: VoteType.NONE
- }
-
- private suspend fun createBucket(pluginUrl: String) {
- val url =
- "${apiDomain}/create?namespace=cs3-votes&key=${transformUrl(pluginUrl)}&value=0&update_lowerbound=-2&update_upperbound=2&enable_reset=0"
- Log.d(LOGKEY, "Requesting: $url")
- app.get(url)
- }
-
- fun canVote(pluginUrl: String): Boolean {
- if (!PluginManager.urlPlugins.contains(pluginUrl)) return false
- return true
- }
-
- private val voteLock = Mutex()
- suspend fun vote(pluginUrl: String, requestType: VoteType): Int {
- // Prevent multiple requests at the same time.
- voteLock.withLock {
- if (!canVote(pluginUrl)) {
- main {
- Toast.makeText(context, R.string.extension_install_first, Toast.LENGTH_SHORT)
- .show()
- }
- return getVotes(pluginUrl)
- }
-
- val savedType: VoteType =
- getKey("cs3-votes/${transformUrl(pluginUrl)}") ?: VoteType.NONE
-
- val newType = if (requestType == savedType) VoteType.NONE else requestType
- val changeValue = if (requestType == savedType) {
- -requestType.value
- } else if (savedType == VoteType.NONE) {
- requestType.value
- } else if (savedType != requestType) {
- -savedType.value + requestType.value
- } else 0
-
- // Pre-emptively set vote key
- setKey("cs3-votes/${transformUrl(pluginUrl)}", newType)
-
- val url =
- "${apiDomain}/update/cs3-votes/${transformUrl(pluginUrl)}?amount=${changeValue}"
- Log.d(LOGKEY, "Requesting: $url")
- val res = app.get(url).parsedSafe()?.value
-
- if (res == null) {
- // "Refund" key if the response is invalid
- setKey("cs3-votes/${transformUrl(pluginUrl)}", savedType)
- } else {
- votesCache[pluginUrl] = res
- }
- return res ?: 0
- }
- }
-
- private data class Result(
- val value: Int?
- )
-}
\ 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/subtitles/AbstractSubtitleEntities.kt b/app/src/main/java/com/lagradost/cloudstream3/subtitles/AbstractSubtitleEntities.kt
index f6424c4c..e7e5b857 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/subtitles/AbstractSubtitleEntities.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/subtitles/AbstractSubtitleEntities.kt
@@ -13,8 +13,7 @@ class AbstractSubtitleEntities {
var epNumber: Int? = null,
var seasonNumber: Int? = null,
var year: Int? = null,
- var isHearingImpaired: Boolean = false,
- var headers: Map = emptyMap()
+ var isHearingImpaired: Boolean = false
)
data class SubtitleSearch(
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..2bc39b54 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/OAuth2API.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/OAuth2API.kt
index ef74edfc..0f882f3b 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/OAuth2API.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/OAuth2API.kt
@@ -1,11 +1,9 @@
package com.lagradost.cloudstream3.syncproviders
-import androidx.fragment.app.FragmentActivity
-
interface OAuth2API : AuthAPI {
val key: String
val redirectUrl: String
suspend fun handleRedirect(url: String) : Boolean
- fun authenticate(activity: FragmentActivity?)
+ fun authenticate()
}
\ No newline at end of file
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..606fee97 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,18 @@
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 +20,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 +27,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,13 +45,12 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
}
override fun logOut() {
- requireLibraryRefresh = true
removeAccountKeys()
}
- override fun authenticate(activity: FragmentActivity?) {
+ override fun authenticate() {
val request = "https://anilist.co/api/v2/oauth/authorize?client_id=$key&response_type=token"
- openBrowser(request, activity)
+ openBrowser(request)
}
override suspend fun handleRedirect(url: String): Boolean {
@@ -70,8 +64,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 +140,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 +170,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
fromIntToAnimeStatus(status.status),
status.score,
status.watchedEpisodes
- ).also {
- requireLibraryRefresh = requireLibraryRefresh || it
- }
+ )
}
companion object {
@@ -190,6 +181,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 +219,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
romaji
}
idMal
- coverImage { medium large extraLarge }
+ coverImage { medium large }
averageScore
}
}
@@ -240,7 +232,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
format
id
idMal
- coverImage { medium large extraLarge }
+ coverImage { medium large }
averageScore
title {
english
@@ -300,13 +292,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 +312,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 +335,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 +521,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 +568,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 +595,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 +610,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 +654,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
startedAt { year month day }
updatedAt
progress
- score (format: POINT_100)
+ score
private
media
{
@@ -726,7 +670,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
english
romaji
}
- coverImage { extraLarge large medium }
+ coverImage { medium }
synonyms
nextAiringEpisode {
timeUntilAiring
@@ -759,11 +703,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 +710,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/DropboxApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/DropboxApi.kt
index 7ec168da..f847e0b2 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/DropboxApi.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/DropboxApi.kt
@@ -1,6 +1,5 @@
package com.lagradost.cloudstream3.syncproviders.providers
-import androidx.fragment.app.FragmentActivity
import com.lagradost.cloudstream3.syncproviders.AuthAPI
import com.lagradost.cloudstream3.syncproviders.OAuth2API
@@ -16,7 +15,7 @@ class Dropbox : OAuth2API {
override val icon: Int
get() = TODO("Not yet implemented")
- override fun authenticate(activity: FragmentActivity?) {
+ override fun authenticate() {
TODO("Not yet implemented")
}
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..0d62cbe4 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
@@ -19,7 +19,6 @@ class IndexSubtitleApi : AbstractSubApi {
override fun logOut() {}
-
companion object {
const val host = "https://indexsubtitle.com"
const val TAG = "INDEXSUBS"
@@ -123,7 +122,7 @@ class IndexSubtitleApi : AbstractSubApi {
type = if (seasonNum > 0) TvType.TvSeries else TvType.Movie,
epNumber = epNum,
seasonNumber = seasonNum,
- year = yearNum,
+ year = yearNum
)
)
}
@@ -132,7 +131,7 @@ class IndexSubtitleApi : AbstractSubApi {
document.select("div.my-3.p-3 div.media").map { block ->
if (seasonNum > 0) {
- val name = block.select("strong.text-primary, strong.text-info").text().trim()
+ val name = block.select("strong.text-primary").text().trim()
val season = getOrdinal(seasonNum)
if ((block.selectFirst("a")?.attr("href")
?.contains(
@@ -206,7 +205,7 @@ class IndexSubtitleApi : AbstractSubApi {
.trim()
.contains("$queryLang")
) {
- var name = block.select("strong.text-primary, strong.text-info").text().trim()
+ var name = block.select("strong.text-primary").text().trim()
val link = fixUrl(block.selectFirst("a")!!.attr("href"))
if (seasonNum > 0) {
when {
@@ -241,9 +240,9 @@ 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()
+ block.selectFirst("strong.d-block.text-primary")?.text()?.trim().toString()
if (seasonNum!! > 0) {
if (isRightEps(name, seasonNum, epNum)) {
fixUrl(block.selectFirst("a")!!.attr("href"))
@@ -253,7 +252,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..ea27720a 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,23 +1,17 @@
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
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 +30,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 +89,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
fromIntToAnimeStatus(status.status),
status.score,
status.watchedEpisodes
- ).also {
- requireLibraryRefresh = requireLibraryRefresh || it
- }
+ )
}
data class MalAnime(
@@ -257,45 +247,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,14 +274,14 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
switchToNewAccount()
storeToken(res)
val user = getMalUser()
- requireLibraryRefresh = true
+ setKey(MAL_SHOULD_UPDATE_LIST, true)
return user != null
}
}
return false
}
- override fun authenticate(activity: FragmentActivity?) {
+ override fun authenticate() {
// It is recommended to use a URL-safe string as code_verifier.
// See section 4 of RFC 7636 for more details.
@@ -339,7 +294,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
val codeChallenge = codeVerifier
val request =
"$mainUrl/v1/oauth2/authorize?response_type=code&client_id=$key&code_challenge=$codeChallenge&state=RequestID$requestId"
- openBrowser(request, activity)
+ openBrowser(request)
}
private var requestId = 0
@@ -352,10 +307,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 +328,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
).text
storeToken(res)
} catch (e: Exception) {
- logError(e)
+ e.printStackTrace()
}
}
@@ -427,24 +381,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 +412,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 +439,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 +556,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..bd918911 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,8 +279,8 @@ 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) {
- logError(t)
+ } catch (e : Exception) {
+ logError(e)
}
if (itemCount != null && itemCount - currentIdIndex == 1 && !isLoadingMore) {
@@ -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/WebviewFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/WebviewFragment.kt
index 19e24f74..d773b3a2 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/WebviewFragment.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/WebviewFragment.kt
@@ -4,19 +4,17 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import android.webkit.JavascriptInterface
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.fragment.app.Fragment
-import androidx.fragment.app.FragmentActivity
import androidx.navigation.fragment.findNavController
-import com.lagradost.cloudstream3.MainActivity
import com.lagradost.cloudstream3.R
-import com.lagradost.cloudstream3.USER_AGENT
import com.lagradost.cloudstream3.network.WebViewResolver
+import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringRepo
import com.lagradost.cloudstream3.utils.AppUtils.loadRepository
import kotlinx.android.synthetic.main.fragment_webview.*
+import java.net.URI
class WebviewFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -31,8 +29,16 @@ class WebviewFragment : Fragment() {
request: WebResourceRequest?
): Boolean {
val requestUrl = request?.url.toString()
- val performedAction = MainActivity.handleAppIntentUrl(activity, requestUrl, true)
- if (performedAction) {
+ val repoUrl = if (requestUrl.startsWith("https://cs.repo")) {
+ "https://" + requestUrl.substringAfter("?")
+ } else if (URI(requestUrl).scheme == appStringRepo) {
+ requestUrl.replaceFirst(appStringRepo, "https")
+ } else {
+ null
+ }
+
+ if (repoUrl != null) {
+ activity?.loadRepository(repoUrl)
findNavController().popBackStack()
return true
}
@@ -40,15 +46,11 @@ class WebviewFragment : Fragment() {
return super.shouldOverrideUrlLoading(view, request)
}
}
+ web_view.settings.javaScriptEnabled = true
+ web_view.settings.domStorageEnabled = true
WebViewResolver.webViewUserAgent = web_view.settings.userAgentString
-
- web_view.addJavascriptInterface(RepoApi(activity), "RepoApi")
- web_view.settings.javaScriptEnabled = true
- web_view.settings.userAgentString = USER_AGENT
- web_view.settings.domStorageEnabled = true
-// WebView.setWebContentsDebuggingEnabled(true)
-
+// web_view.settings.userAgentString = USER_AGENT
web_view.loadUrl(url)
}
@@ -67,11 +69,4 @@ class WebviewFragment : Fragment() {
putString(WEBVIEW_URL, webViewUrl)
}
}
-
- private class RepoApi(val activity: FragmentActivity?) {
- @JavascriptInterface
- fun installRepo(repoUrl: String) {
- activity?.loadRepository(repoUrl)
- }
- }
}
\ No newline at end of file
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..7ea360a8 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
@@ -37,11 +38,6 @@ import com.lagradost.cloudstream3.utils.VideoDownloadManager
import kotlinx.android.synthetic.main.fragment_downloads.*
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
const val DOWNLOAD_NAVIGATE_TO = "downloadpage"
@@ -179,9 +175,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 = context?.isTvSettings() == true
download_stream_button?.setOnClickListener {
val dialog =
Dialog(it.context ?: return@setOnClickListener, R.style.AlertDialogCustom)
@@ -189,29 +183,10 @@ class DownloadFragment : Fragment() {
dialog.show()
- // If user has clicked the switch do not interfere
- var preventAutoSwitching = false
- dialog.hls_switch?.setOnClickListener {
- preventAutoSwitching = true
- }
-
- fun activateSwitchOnHls(text: String?) {
- dialog.hls_switch?.isChecked = normalSafeApiCall {
- URI(text).path?.substringAfterLast(".")?.contains("m3u")
- } == true
- }
-
- dialog.stream_referer?.doOnTextChanged { text, _, _, _ ->
- if (!preventAutoSwitching)
- activateSwitchOnHls(text?.toString())
- }
-
(activity?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager?)?.primaryClip?.getItemAt(
0
)?.text?.toString()?.let { copy ->
- val fixedText = copy.trim()
- dialog.stream_url?.setText(fixedText)
- activateSwitchOnHls(fixedText)
+ dialog.stream_url?.setText(copy)
}
dialog.apply_btt?.setOnClickListener {
@@ -225,10 +200,9 @@ 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
+ referer = referer
)
)
)
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadHeaderAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadHeaderAdapter.kt
index 29bb303a..fde490e2 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadHeaderAdapter.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadHeaderAdapter.kt
@@ -148,10 +148,10 @@ class DownloadHeaderAdapter(
),
mbString
)
- } catch (t : Throwable) {
+ } catch (e : Exception) {
// you probably formatted incorrectly
extraInfo.text = "Error"
- logError(t)
+ logError(e)
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt
index 3a74a715..8d969b0a 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt
@@ -112,9 +112,9 @@ class DownloadViewModel : ViewModel() {
_usedBytes.postValue(localTotalBytes - localBytesAvailable - localDownloadedBytes)
_availableBytes.postValue(localBytesAvailable)
_downloadBytes.postValue(localDownloadedBytes)
- } catch (t : Throwable) {
+ } catch (e : Exception) {
_downloadBytes.postValue(0)
- logError(t)
+ logError(e)
}
_headerCards.postValue(visual)
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..975545ee 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,94 +237,38 @@ 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>> {
- // This list should be same order as home screen to aid navigation
+ anime: MaterialButton?,
+ cartoons: MaterialButton?,
+ tvs: MaterialButton?,
+ docs: MaterialButton?,
+ movies: MaterialButton?,
+ asian: MaterialButton?,
+ livestream: MaterialButton?,
+ nsfw: MaterialButton?,
+ others: MaterialButton?,
+ ): List>> {
return listOf(
- Pair(movies, listOf(TvType.Movie, TvType.Torrent)),
- Pair(tvs, listOf(TvType.TvSeries)),
Pair(anime, listOf(TvType.Anime, TvType.OVA, TvType.AnimeMovie)),
- Pair(asian, listOf(TvType.AsianDrama)),
Pair(cartoons, listOf(TvType.Cartoon)),
+ Pair(tvs, listOf(TvType.TvSeries)),
Pair(docs, listOf(TvType.Documentary)),
+ Pair(movies, listOf(TvType.Movie, TvType.Torrent)),
+ Pair(asian, listOf(TvType.AsianDrama)),
Pair(livestream, listOf(TvType.Live)),
Pair(nsfw, listOf(TvType.NSFW)),
Pair(others, listOf(TvType.Others)),
)
}
- 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