mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
Merge branch 'master' into merge-reduplicated
This commit is contained in:
commit
ff7b468f54
205 changed files with 1388 additions and 28369 deletions
65
.github/site-list.py
vendored
65
.github/site-list.py
vendored
|
@ -1,65 +0,0 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
from glob import glob
|
||||
from re import findall, compile, DOTALL
|
||||
from json import dump, load
|
||||
from typing import List, Dict
|
||||
|
||||
# Globals
|
||||
JSON_PATH = "docs/providers.json"
|
||||
GLOB_ANIME = "app/src/main/java/com/lagradost/cloudstream3/animeproviders/*Provider.kt"
|
||||
GLOB_MOVIE = "app/src/main/java/com/lagradost/cloudstream3/movieproviders/*Provider.kt"
|
||||
URL_REGEX = compile("override\sva[lr]\smainUrl[^\"']+[\"'](https?://[a-zA-Z0-9\.-/]+)[\"']")
|
||||
FILENAME_REGEX = compile("([A-Za-z0-9]+)(?:.kt)$")
|
||||
PROVIDER_CLASSNAME_REGEX = compile("(?<=class\s)([a-zA-Z]+)(?=\s:\sMainAPI\(\))")
|
||||
NAME_REGEX = compile("override\sva[lr]\sname[^\"']+[\"']([a-zA-Z-.\s]+)")
|
||||
LANG_REGEX = compile("override\sva[lr]\slang[^\"']+[\"']([a-zA-Z]+)")
|
||||
|
||||
old_sites: Dict[str, Dict] = load(open(JSON_PATH, "r", encoding="utf-8"))
|
||||
sites: Dict[str, Dict] = {}
|
||||
|
||||
animelist = glob(GLOB_ANIME)
|
||||
movielist = glob(GLOB_MOVIE)
|
||||
allProvidersList = animelist + movielist
|
||||
|
||||
# parse all *Provider.kt files
|
||||
for path in allProvidersList:
|
||||
with open(path, "r", encoding='utf-8') as file:
|
||||
try:
|
||||
site_text: str = file.read()
|
||||
filename: str = findall(FILENAME_REGEX, path)[0]
|
||||
name: str = [*findall(PROVIDER_CLASSNAME_REGEX, site_text), filename][0]
|
||||
provider_url: str = [*findall(URL_REGEX, site_text), ""][0]
|
||||
lang: str = [*findall(LANG_REGEX, site_text), "en"][0]
|
||||
|
||||
if name in old_sites.keys(): # if already in previous list use old status and name
|
||||
sites[name] = {
|
||||
"name": old_sites[name]['name'],
|
||||
"url": provider_url if provider_url else old_sites[name]['url'],
|
||||
"status": old_sites[name]['status'],
|
||||
"language": lang
|
||||
}
|
||||
else: # if not in previous list add with new data
|
||||
display_name: str = [*findall(NAME_REGEX, site_text), name][0]
|
||||
if display_name.endswith("Provider"):
|
||||
display_name = display_name[:-len("Provider")]
|
||||
sites[name] = {
|
||||
"name": display_name,
|
||||
"url": provider_url if provider_url else "",
|
||||
"status": 1,
|
||||
"language": lang
|
||||
}
|
||||
|
||||
except Exception as ex:
|
||||
print("Error => {0}: {1}".format(path, ex))
|
||||
|
||||
# add sites from old_sites that are missing in new list
|
||||
for name in old_sites.keys():
|
||||
if name not in sites.keys():
|
||||
sites[name] = {
|
||||
"name": old_sites[name]['name'],
|
||||
"url": old_sites[name]['url'],
|
||||
"status": old_sites[name]['status']
|
||||
}
|
||||
|
||||
dump(sites, open(JSON_PATH, "w+", encoding="utf-8"), indent=4, sort_keys=True)
|
51
.github/workflows/prerelease.yml
vendored
51
.github/workflows/prerelease.yml
vendored
|
@ -31,25 +31,34 @@ jobs:
|
|||
run: |
|
||||
COMMIT_HASH="$(git log -1 --format='%H')"
|
||||
sed -i "s/<string name=\"prerelease_commit_hash\" translatable=\"false\">unknown_prerelease<\/string>/<string name=\"prerelease_commit_hash\">$COMMIT_HASH<\/string>/g" app/src/main/res/values/strings.xml
|
||||
- name: Decode Keystore
|
||||
env:
|
||||
ENCODED_STRING: ${{ secrets.KEYSTORE }}
|
||||
run: |
|
||||
TMP_KEYSTORE_FILE_PATH="${RUNNER_TEMP}"/keystore
|
||||
mkdir -p "${TMP_KEYSTORE_FILE_PATH}"
|
||||
echo $ENCODED_STRING | base64 -di > "${TMP_KEYSTORE_FILE_PATH}"/prerelease_keystore.keystore
|
||||
# - name: Decode Keystore
|
||||
# env:
|
||||
# ENCODED_STRING: ${{ secrets.KEYSTORE }}
|
||||
# run: |
|
||||
# TMP_KEYSTORE_FILE_PATH="${RUNNER_TEMP}"/keystore
|
||||
# mkdir -p "${TMP_KEYSTORE_FILE_PATH}"
|
||||
# echo $ENCODED_STRING | base64 -di > "${TMP_KEYSTORE_FILE_PATH}"/prerelease_keystore.keystore
|
||||
- name: Run Gradle
|
||||
run: ./gradlew app:assemblePrerelease
|
||||
env:
|
||||
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
|
||||
#- name: Create pre-release
|
||||
# uses: "marvinpinto/action-automatic-releases@latest"
|
||||
# with:
|
||||
# repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
# automatic_release_tag: "pre-release"
|
||||
# prerelease: true
|
||||
# title: "Pre-release Build"
|
||||
# files: |
|
||||
# app/build/outputs/apk/prerelease/*.apk
|
||||
run: |
|
||||
./gradlew assembleDebug
|
||||
./gradlew androidSourcesJar
|
||||
./gradlew makeJar
|
||||
# env:
|
||||
# SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||
# SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||
# SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD }}
|
||||
- name: Display tree
|
||||
run: |
|
||||
sudo apt install tree -y 2>&1 > /dev/null
|
||||
tree app/build
|
||||
- name: Create pre-release
|
||||
uses: "marvinpinto/action-automatic-releases@latest"
|
||||
with:
|
||||
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
automatic_release_tag: "pre-release"
|
||||
prerelease: true
|
||||
title: "Pre-release Build"
|
||||
files: |
|
||||
app/build/outputs/apk/debug/*.apk
|
||||
app/build/libs/app-sources.jar
|
||||
app/build/classes.jar
|
||||
|
|
32
.github/workflows/site_list.yml
vendored
32
.github/workflows/site_list.yml
vendored
|
@ -1,32 +0,0 @@
|
|||
name: Create site list
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
paths:
|
||||
- 'app/src/main/java/com/lagradost/cloudstream3/*providers/*Provider.kt'
|
||||
- '.github/workflows/site_list.yml'
|
||||
- '.github/site-list.py'
|
||||
- 'docs/providers.json'
|
||||
- 'app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt'
|
||||
|
||||
concurrency:
|
||||
group: "site-list"
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
create:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Edit providers.json
|
||||
run: |
|
||||
python3 .github/site-list.py
|
||||
- name: Commit to the repo
|
||||
run: |
|
||||
git config user.name "GitHub Actions"
|
||||
git config user.email "actions@github.com"
|
||||
git add .
|
||||
# "echo" returns true so the build succeeds, even if no changed files
|
||||
git commit -m 'chore(docs): update list of sites' || echo
|
||||
git push
|
|
@ -51,22 +51,22 @@ android {
|
|||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
debuggable false
|
||||
minifyEnabled false
|
||||
shrinkResources false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
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'
|
||||
}
|
||||
// release {
|
||||
// debuggable false
|
||||
// minifyEnabled false
|
||||
// shrinkResources false
|
||||
// proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
// }
|
||||
// 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'
|
||||
// }
|
||||
debug {
|
||||
debuggable true
|
||||
applicationIdSuffix ".debug"
|
||||
|
@ -83,6 +83,10 @@ android {
|
|||
jvmTarget = '1.8'
|
||||
freeCompilerArgs = ['-Xjvm-default=compatibility']
|
||||
}
|
||||
lintOptions {
|
||||
checkReleaseBuilds false
|
||||
abortOnError false
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
|
@ -183,4 +187,16 @@ dependencies {
|
|||
implementation 'com.github.TeamNewPipe:NewPipeExtractor:master-SNAPSHOT'
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||
|
||||
}
|
||||
|
||||
task androidSourcesJar(type: Jar) {
|
||||
getArchiveClassifier().set('sources')
|
||||
from android.sourceSets.main.java.srcDirs//full sources
|
||||
}
|
||||
|
||||
task makeJar(type: Copy) {
|
||||
from('build/intermediates/compile_app_classes_jar/debug') // after modifying here, you can export. Jar
|
||||
into('build') // output location
|
||||
include('classes.jar') // the classes file of the imported rack package
|
||||
dependsOn build
|
||||
}
|
|
@ -7,6 +7,7 @@
|
|||
<uses-permission android:name="android.permission.INTERNET" /> <!-- unless you only use cs3 as a player for downloaded stuff, you need this -->
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <!-- Downloads -->
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <!-- Downloads on low api devices-->
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> <!-- Plugin API -->
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <!-- some dependency needs this -->
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> <!-- Used for player vertical slide-->
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> <!--Used for app update-->
|
||||
|
@ -75,110 +76,6 @@
|
|||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter android:exported="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:scheme="https" android:host="elifilms.net" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="www23.estrenosdoramas.net" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="pelisplus.icu" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="pelisplushd.net" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="pelismart.com" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="melomovie.com" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="doramasyt.com" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="cinecalidad.lol" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="cuevana3.me" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="entrepeliculasyseries.nu" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="pelisflix.li" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="seriesflix.video" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="ihavenotv.com" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="www.vmovee.watch" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="allmoviesforyou.net" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="membed.net" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="vf-film.me" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="vf-serie.org" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="french-stream.re" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="asianembed.io" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="asiaflix.app" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="eja.tv/" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="bflix.ru" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="fmovies.to" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="sflix.pro" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="filman.cc" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="sflix.to" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="dopebox.to" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="solarmovie.pe" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="pinoymoviepedia.ru" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="www.pinoy-hd.xyz" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="pinoymovies.es" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="trailers.to" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="2embed.org" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="dramasee.net" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="watchasian.cx" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="185.224.83.103" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="kdramahood.com" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="akwam.to" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="mycima.tv" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="cimanow.cc" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="www.egy.best" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="faselhd.io" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="secretlink.xyz" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="hdm.to" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="theflix.to" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="streamingcommunity.best" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="tantifilm.autos" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="cb01.rip" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="altadefinizione.tienda" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="filmpertutti.photo" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="hdmovie2.art" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="http://167.88.14.149" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="149.56.24.226" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="hdtoday.cc" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="openvids.io" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="94.103.82.88" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="146.19.24.137" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="v2.vidsrc.me" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="uakino.club" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="phimmoichill.net" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="rezka.ag" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="yomovies.plus" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="www.wcostream.com" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="gogoanime.sk" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="allanime.site" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="animekisa.in" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="animeflick.net" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="www3.animeflv.net" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="animefenix.com" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="animeflv.io" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="jkanime.net" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="tenshi.moe" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="wcostream.cc" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="animepahe.com" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="9anime.id" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="www.animeworld.tv" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="zoro.to" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="bestdubbedanime.com" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="monoschinos2.com" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="www.mundodonghua.com" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="kawaiifu.com" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="neonime.watch" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="kuramanime.com" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="65.108.132.145" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="185.231.223.76" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="75.119.159.228" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="45.12.2.2" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="otakudesu.watch" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="animeindo.sbs" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="111.90.143.42" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="tocanime.co" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="olgply.com" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="aniflix.pro" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="kimcartoon.li" android:pathPrefix="/"/>
|
||||
<data android:scheme="https" android:host="xcine.me" android:pathPrefix="/"/>
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
|
|
|
@ -10,10 +10,6 @@ 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.animeproviders.*
|
||||
import com.lagradost.cloudstream3.liveproviders.EjaTv
|
||||
import com.lagradost.cloudstream3.metaproviders.CrossTmdbProvider
|
||||
import com.lagradost.cloudstream3.movieproviders.*
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi
|
||||
|
@ -27,6 +23,7 @@ import okhttp3.Interceptor
|
|||
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"
|
||||
|
@ -43,122 +40,7 @@ object APIHolder {
|
|||
|
||||
private const val defProvider = 0
|
||||
|
||||
val allProviders =
|
||||
arrayListOf(
|
||||
// Movie providers
|
||||
ElifilmsProvider(),
|
||||
EstrenosDoramasProvider(),
|
||||
PelisplusProvider(),
|
||||
PelisplusHDProvider(),
|
||||
PeliSmartProvider(),
|
||||
MeloMovieProvider(), // Captcha for links
|
||||
DoramasYTProvider(),
|
||||
CinecalidadProvider(),
|
||||
CuevanaProvider(),
|
||||
EntrepeliculasyseriesProvider(),
|
||||
PelisflixProvider(),
|
||||
SeriesflixProvider(),
|
||||
IHaveNoTvProvider(), // Documentaries provider
|
||||
VMoveeProvider(),
|
||||
AllMoviesForYouProvider(),
|
||||
VidEmbedProvider(),
|
||||
VfFilmProvider(),
|
||||
VfSerieProvider(),
|
||||
FrenchStreamProvider(),
|
||||
AsianLoadProvider(),
|
||||
AsiaFlixProvider(), // This should be removed in favor of asianembed.io, same source
|
||||
EjaTv(),
|
||||
BflixProvider(),
|
||||
FmoviesToProvider(),
|
||||
SflixProProvider(),
|
||||
FilmanProvider(),
|
||||
SflixProvider(),
|
||||
DopeboxProvider(),
|
||||
SolarmovieProvider(),
|
||||
PinoyMoviePediaProvider(),
|
||||
PinoyHDXyzProvider(),
|
||||
PinoyMoviesEsProvider(),
|
||||
TrailersTwoProvider(),
|
||||
TwoEmbedProvider(),
|
||||
DramaSeeProvider(),
|
||||
WatchAsianProvider(),
|
||||
DramaidProvider(),
|
||||
KdramaHoodProvider(),
|
||||
AkwamProvider(),
|
||||
MyCimaProvider(),
|
||||
CimaNowProvider(),
|
||||
EgyBestProvider(),
|
||||
FaselHDProvider(),
|
||||
SoaptwoDayProvider(),
|
||||
HDMProvider(),// disabled due to cloudflare
|
||||
TheFlixToProvider(),
|
||||
StreamingcommunityProvider(),
|
||||
TantifilmProvider(),
|
||||
CineblogProvider(),
|
||||
IlGenioDelloStreamingProvider(),
|
||||
AltadefinizioneProvider(),
|
||||
FilmpertuttiProvider(),
|
||||
HDMovie5(),
|
||||
RebahinProvider(),
|
||||
LayarKacaProvider(),
|
||||
HDTodayProvider(),
|
||||
OpenVidsProvider(),
|
||||
IdlixProvider(),
|
||||
MultiplexProvider(),
|
||||
VidSrcProvider(),
|
||||
UakinoProvider(),
|
||||
PhimmoichillProvider(),
|
||||
HDrezkaProvider(),
|
||||
YomoviesProvider(),
|
||||
DubokuProvider(),
|
||||
KisskhProvider(),
|
||||
|
||||
// Metadata providers
|
||||
//TmdbProvider(),
|
||||
CrossTmdbProvider(),
|
||||
|
||||
// Anime providers
|
||||
WatchCartoonOnlineProvider(),
|
||||
GogoanimeProvider(),
|
||||
AllAnimeProvider(),
|
||||
AnimekisaProvider(),
|
||||
//ShiroProvider(), // v2 fucked me
|
||||
AnimeFlickProvider(),
|
||||
AnimeflvnetProvider(),
|
||||
AnimefenixProvider(),
|
||||
AnimeflvIOProvider(),
|
||||
JKAnimeProvider(),
|
||||
TenshiProvider(),
|
||||
WcoProvider(),
|
||||
AnimePaheProvider(),
|
||||
NineAnimeProvider(),
|
||||
AnimeWorldProvider(),
|
||||
AnimeSaturnProvider(),
|
||||
AniPlayProvider(),
|
||||
ZoroProvider(),
|
||||
DubbedAnimeProvider(),
|
||||
MonoschinosProvider(),
|
||||
MundoDonghuaProvider(),
|
||||
KawaiifuProvider(), // disabled due to cloudflare
|
||||
NeonimeProvider(),
|
||||
KuramanimeProvider(),
|
||||
OploverzProvider(),
|
||||
GomunimeProvider(),
|
||||
NontonAnimeIDProvider(),
|
||||
KuronimeProvider(),
|
||||
OtakudesuProvider(),
|
||||
AnimeIndoProvider(),
|
||||
AnimeSailProvider(),
|
||||
TocanimeProvider(),
|
||||
WcofunProvider(),
|
||||
//MultiAnimeProvider(),
|
||||
NginxProvider(),
|
||||
OlgplyProvider(),
|
||||
AniflixProvider(),
|
||||
KimCartoonProvider(),
|
||||
XcineProvider(),
|
||||
SuperStream()
|
||||
)
|
||||
val allProviders: MutableList<MainAPI> = arrayListOf()
|
||||
|
||||
|
||||
fun initAll() {
|
||||
|
|
|
@ -10,6 +10,7 @@ import android.view.KeyEvent
|
|||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.WindowManager
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.isVisible
|
||||
|
@ -39,7 +40,6 @@ import com.lagradost.cloudstream3.CommonActivity.onUserLeaveHint
|
|||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||
import com.lagradost.cloudstream3.CommonActivity.updateLocale
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||
import com.lagradost.cloudstream3.network.initClient
|
||||
import com.lagradost.cloudstream3.receivers.VideoDownloadRestartReceiver
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.OAuth2Apis
|
||||
|
@ -52,16 +52,12 @@ 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.isTvSettings
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsGeneral
|
||||
import com.lagradost.cloudstream3.ui.setup.HAS_DONE_SETUP_KEY
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.loadCache
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.loadResult
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
|
||||
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
|
||||
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
||||
import com.lagradost.cloudstream3.utils.DataStore.removeKey
|
||||
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
||||
|
@ -76,18 +72,17 @@ 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.nicehttp.Requests
|
||||
import com.lagradost.nicehttp.ResponseParser
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
import kotlinx.android.synthetic.main.fragment_result_swipe.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.schabi.newpipe.extractor.NewPipe
|
||||
import java.io.File
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.reflect.KClass
|
||||
import com.lagradost.cloudstream3.plugins.PluginManager
|
||||
import com.lagradost.cloudstream3.plugins.RepositoryManager
|
||||
import com.lagradost.cloudstream3.ui.settings.extensions.RepositoryData
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||
|
||||
|
||||
const val VLC_PACKAGE = "org.videolan.vlc"
|
||||
|
@ -333,7 +328,26 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
val str = intent.dataString
|
||||
loadCache()
|
||||
if (str != null) {
|
||||
if (str.contains(appString)) {
|
||||
if (str.startsWith("https://cs.repo")) {
|
||||
val realUrl = "https://" + str.substringAfter("?")
|
||||
println("Repository url: $realUrl")
|
||||
ioSafe {
|
||||
val repo = RepositoryManager.parseRepository(realUrl) ?: return@ioSafe
|
||||
RepositoryManager.addRepository(
|
||||
RepositoryData(
|
||||
repo.name,
|
||||
realUrl
|
||||
)
|
||||
)
|
||||
main {
|
||||
showToast(
|
||||
this,
|
||||
this.getString(R.string.player_loaded_subtitles, repo.name),
|
||||
Toast.LENGTH_LONG
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if (str.contains(appString)) {
|
||||
for (api in OAuth2Apis) {
|
||||
if (str.contains("/${api.redirectUrl}")) {
|
||||
val activity = this
|
||||
|
@ -402,66 +416,22 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
}
|
||||
}
|
||||
|
||||
fun test() {
|
||||
/*thread {
|
||||
val youtubeLink = "https://www.youtube.com/watch?v=Zxem9rqJ5S0"
|
||||
|
||||
val url = YoutubeStreamLinkHandlerFactory.getInstance().fromUrl(youtubeLink)
|
||||
println("ID:::: ${url.id}")
|
||||
NewPipe.init(DownloaderTestImpl.getInstance())
|
||||
val service = ServiceList.YouTube
|
||||
val s = object : YoutubeStreamExtractor(
|
||||
service,
|
||||
url
|
||||
) {
|
||||
|
||||
}
|
||||
s.fetchPage()
|
||||
val streams = s.videoStreams
|
||||
println("STREAMS: ${streams.map { "url = "+ it.url + " extra= " + it.height + "|" + it.isVideoOnly + "\n" }}")
|
||||
}*/
|
||||
/*
|
||||
runBlocking {
|
||||
|
||||
val query = """
|
||||
query {
|
||||
searchShows(search: "spider", limit: 10) {
|
||||
id
|
||||
name
|
||||
originalName
|
||||
}
|
||||
}
|
||||
"""
|
||||
val data =
|
||||
mapOf(
|
||||
"query" to query,
|
||||
//"variables" to
|
||||
// mapOf(
|
||||
// "name" to name,
|
||||
// ).toJson()
|
||||
)
|
||||
val txt = app.post(
|
||||
"http://api.anime-skip.com/graphql",
|
||||
headers = mapOf(
|
||||
"X-Client-ID" to "",
|
||||
"Content-Type" to "application/json",
|
||||
"Accept" to "application/json",
|
||||
),
|
||||
json = data
|
||||
)
|
||||
println("TEXT: $txt")
|
||||
}*/
|
||||
/*runBlocking {
|
||||
//https://test.api.anime-skip.com/graphiql
|
||||
val txt = app.get(
|
||||
"https://api.anime-skip.com/status",
|
||||
headers = mapOf("X-Client-ID" to "")
|
||||
)
|
||||
println("TEXT: $txt")
|
||||
}*/
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
app.initClient(this)
|
||||
|
||||
PluginManager.loadAllLocalPlugins(applicationContext)
|
||||
PluginManager.loadAllOnlinePlugins(applicationContext)
|
||||
|
||||
// ioSafe {
|
||||
// val plugins =
|
||||
// RepositoryParser.getRepoPlugins("https://raw.githubusercontent.com/recloudstream/TestPlugin/master/repo.json")
|
||||
// ?: emptyList()
|
||||
// plugins.map {
|
||||
// println("Load plugin: ${it.name} ${it.url}")
|
||||
// RepositoryParser.loadSiteTemp(applicationContext, it.url, it.name)
|
||||
// }
|
||||
// }
|
||||
|
||||
// init accounts
|
||||
for (api in accountManagers) {
|
||||
api.init()
|
||||
|
@ -480,118 +450,12 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
SearchResultBuilder.updateCache(this)
|
||||
|
||||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
val downloadFromGithub = try {
|
||||
settingsManager.getBoolean(getString(R.string.killswitch_key), true)
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
false
|
||||
}
|
||||
|
||||
// must give benenes to get beta providers
|
||||
val hasBenene = try {
|
||||
val count = settingsManager.getInt(getString(R.string.benene_count), 0)
|
||||
count > 30
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
false
|
||||
}
|
||||
|
||||
try {
|
||||
getKey<Array<SettingsGeneral.CustomSite>>(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
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
apis = allProviders
|
||||
APIHolder.apiMap = null
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
}
|
||||
|
||||
// this pulls the latest data so ppl don't have to update to simply change provider url
|
||||
if (downloadFromGithub) {
|
||||
try {
|
||||
runBlocking {
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val cacheStr: String? = getKey(PROVIDER_STATUS_KEY)
|
||||
val cache: HashMap<String, ProvidersInfoJson>? =
|
||||
cacheStr?.let { tryParseJson(cacheStr) }
|
||||
if (cache != null) {
|
||||
// if cache is found then spin up a new request, but dont wait
|
||||
main {
|
||||
try {
|
||||
val txt = app.get(PROVIDER_STATUS_URL).text
|
||||
val newCache =
|
||||
tryParseJson<HashMap<String, ProvidersInfoJson>>(txt)
|
||||
setKey(PROVIDER_STATUS_KEY, txt)
|
||||
MainAPI.overrideData = newCache // update all new providers
|
||||
|
||||
initAll()
|
||||
for (api in apis) { // update current providers
|
||||
newCache?.get(api.javaClass.simpleName)
|
||||
?.let { data ->
|
||||
api.overrideWithNewData(data)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
}
|
||||
}
|
||||
cache
|
||||
} else {
|
||||
// if it is the first time the user has used the app then wait for a request to update all providers
|
||||
val txt = app.get(PROVIDER_STATUS_URL).text
|
||||
setKey(PROVIDER_STATUS_KEY, txt)
|
||||
val newCache = tryParseJson<HashMap<String, ProvidersInfoJson>>(txt)
|
||||
newCache
|
||||
}?.let { providersJsonMap ->
|
||||
MainAPI.overrideData = providersJsonMap
|
||||
initAll()
|
||||
val acceptableProviders =
|
||||
providersJsonMap.filter { it.value.status == PROVIDER_STATUS_OK || it.value.status == PROVIDER_STATUS_SLOW }
|
||||
.map { it.key }.toSet()
|
||||
|
||||
val restrictedApis =
|
||||
if (hasBenene) providersJsonMap.filter { it.value.status == PROVIDER_STATUS_BETA_ONLY }
|
||||
.map { it.key }.toSet() else emptySet()
|
||||
|
||||
apis = allProviders.filter { api ->
|
||||
val name = api.javaClass.simpleName
|
||||
// if the provider does not exist in the json file, then it is shown by default
|
||||
!providersJsonMap.containsKey(name) || acceptableProviders.contains(
|
||||
name
|
||||
) || restrictedApis.contains(name)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
initAll()
|
||||
apis = allProviders
|
||||
e.printStackTrace()
|
||||
logError(e)
|
||||
}
|
||||
} else {
|
||||
initAll()
|
||||
apis = allProviders
|
||||
}
|
||||
|
||||
initAll()
|
||||
apis = allProviders
|
||||
|
||||
loadThemes(this)
|
||||
updateLocale()
|
||||
app.initClient(this)
|
||||
super.onCreate(savedInstanceState)
|
||||
try {
|
||||
if (isCastApiAvailable()) {
|
||||
|
@ -649,7 +513,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
}
|
||||
|
||||
loadCache()
|
||||
test()
|
||||
updateHasTrailers()
|
||||
/*nav_view.setOnNavigationItemSelectedListener { item ->
|
||||
when (item.itemId) {
|
||||
|
|
|
@ -1,404 +0,0 @@
|
|||
package com.lagradost.cloudstream3.animeproviders
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addActors
|
||||
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.M3u8Helper
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
import org.jsoup.Jsoup
|
||||
import org.mozilla.javascript.Context
|
||||
import org.mozilla.javascript.Scriptable
|
||||
import java.net.URI
|
||||
import java.net.URLDecoder
|
||||
|
||||
|
||||
class AllAnimeProvider : MainAPI() {
|
||||
override var mainUrl = "https://allanime.site"
|
||||
override var name = "AllAnime"
|
||||
override val hasQuickSearch = false
|
||||
override val hasMainPage = true
|
||||
|
||||
private fun getStatus(t: String): ShowStatus {
|
||||
return when (t) {
|
||||
"Finished" -> ShowStatus.Completed
|
||||
"Releasing" -> ShowStatus.Ongoing
|
||||
else -> ShowStatus.Completed
|
||||
}
|
||||
}
|
||||
|
||||
override val supportedTypes = setOf(TvType.Anime, TvType.AnimeMovie)
|
||||
|
||||
private data class Data(
|
||||
@JsonProperty("shows") val shows: Shows
|
||||
)
|
||||
|
||||
private data class Shows(
|
||||
@JsonProperty("pageInfo") val pageInfo: PageInfo,
|
||||
@JsonProperty("edges") val edges: List<Edges>,
|
||||
@JsonProperty("__typename") val _typename: String
|
||||
)
|
||||
|
||||
private data class Edges(
|
||||
@JsonProperty("_id") val Id: String?,
|
||||
@JsonProperty("name") val name: String,
|
||||
@JsonProperty("englishName") val englishName: String?,
|
||||
@JsonProperty("nativeName") val nativeName: String?,
|
||||
@JsonProperty("thumbnail") val thumbnail: String?,
|
||||
@JsonProperty("type") val type: String?,
|
||||
@JsonProperty("season") val season: Season?,
|
||||
@JsonProperty("score") val score: Double?,
|
||||
@JsonProperty("airedStart") val airedStart: AiredStart?,
|
||||
@JsonProperty("availableEpisodes") val availableEpisodes: AvailableEpisodes?,
|
||||
@JsonProperty("availableEpisodesDetail") val availableEpisodesDetail: AvailableEpisodesDetail?,
|
||||
@JsonProperty("studios") val studios: List<String>?,
|
||||
@JsonProperty("description") val description: String?,
|
||||
@JsonProperty("status") val status: String?,
|
||||
)
|
||||
|
||||
private data class AvailableEpisodes(
|
||||
@JsonProperty("sub") val sub: Int,
|
||||
@JsonProperty("dub") val dub: Int,
|
||||
@JsonProperty("raw") val raw: Int
|
||||
)
|
||||
|
||||
private data class AiredStart(
|
||||
@JsonProperty("year") val year: Int,
|
||||
@JsonProperty("month") val month: Int,
|
||||
@JsonProperty("date") val date: Int
|
||||
)
|
||||
|
||||
private data class Season(
|
||||
@JsonProperty("quarter") val quarter: String,
|
||||
@JsonProperty("year") val year: Int
|
||||
)
|
||||
|
||||
private data class PageInfo(
|
||||
@JsonProperty("total") val total: Int,
|
||||
@JsonProperty("__typename") val _typename: String
|
||||
)
|
||||
|
||||
private data class AllAnimeQuery(
|
||||
@JsonProperty("data") val data: Data
|
||||
)
|
||||
|
||||
data class RandomMain(
|
||||
@JsonProperty("data") var data: DataRan? = DataRan()
|
||||
)
|
||||
|
||||
data class DataRan(
|
||||
@JsonProperty("queryRandomRecommendation") var queryRandomRecommendation: ArrayList<QueryRandomRecommendation> = arrayListOf()
|
||||
)
|
||||
|
||||
data class QueryRandomRecommendation(
|
||||
@JsonProperty("_id") val Id: String? = null,
|
||||
@JsonProperty("name") val name: String? = null,
|
||||
@JsonProperty("englishName") val englishName: String? = null,
|
||||
@JsonProperty("nativeName") val nativeName: String? = null,
|
||||
@JsonProperty("thumbnail") val thumbnail: String? = null,
|
||||
@JsonProperty("airedStart") val airedStart: String? = null,
|
||||
@JsonProperty("availableChapters") val availableChapters: String? = null,
|
||||
@JsonProperty("availableEpisodes") val availableEpisodes: String? = null,
|
||||
@JsonProperty("__typename") val _typename: String? = null
|
||||
)
|
||||
|
||||
override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse {
|
||||
val items = ArrayList<HomePageList>()
|
||||
val urls = listOf(
|
||||
// Pair(
|
||||
// "Top Anime",
|
||||
// """$mainUrl/graphql?variables={"type":"anime","size":30,"dateRange":30}&extensions={"persistedQuery":{"version":1,"sha256Hash":"276d52ba09ca48ce2b8beb3affb26d9d673b22f9d1fd4892aaa39524128bc745"}}"""
|
||||
// ),
|
||||
// "countryOrigin":"JP" for Japanese only
|
||||
Pair(
|
||||
"Recently updated",
|
||||
"""$mainUrl/graphql?variables={"search":{"allowAdult":false,"allowUnknown":false},"limit":30,"page":1,"translationType":"dub","countryOrigin":"ALL"}&extensions={"persistedQuery":{"version":1,"sha256Hash":"d2670e3e27ee109630991152c8484fce5ff5e280c523378001f9a23dc1839068"}}"""
|
||||
),
|
||||
)
|
||||
|
||||
val random =
|
||||
"""$mainUrl/graphql?variables={"format":"anime"}&extensions={"persistedQuery":{"version":1,"sha256Hash":"21ac672633498a3698e8f6a93ce6c2b3722b29a216dcca93363bf012c360cd54"}}"""
|
||||
val ranlink = app.get(random).text
|
||||
val jsonran = parseJson<RandomMain>(ranlink)
|
||||
val ranhome = jsonran.data?.queryRandomRecommendation?.map {
|
||||
newAnimeSearchResponse(it.name!!, "$mainUrl/anime/${it.Id}", fix = false) {
|
||||
this.posterUrl = it.thumbnail
|
||||
this.otherName = it.nativeName
|
||||
}
|
||||
}
|
||||
|
||||
items.add(HomePageList("Random", ranhome!!))
|
||||
|
||||
urls.apmap { (HomeName, url) ->
|
||||
val test = app.get(url).text
|
||||
val json = parseJson<AllAnimeQuery>(test)
|
||||
val home = ArrayList<SearchResponse>()
|
||||
val results = json.data.shows.edges.filter {
|
||||
// filtering in case there is an anime with 0 episodes available on the site.
|
||||
!(it.availableEpisodes?.raw == 0 && it.availableEpisodes.sub == 0 && it.availableEpisodes.dub == 0)
|
||||
}
|
||||
results.map {
|
||||
home.add(
|
||||
newAnimeSearchResponse(it.name, "$mainUrl/anime/${it.Id}", fix = false) {
|
||||
this.posterUrl = it.thumbnail
|
||||
this.year = it.airedStart?.year
|
||||
this.otherName = it.englishName
|
||||
addDub(it.availableEpisodes?.dub)
|
||||
addSub(it.availableEpisodes?.sub)
|
||||
})
|
||||
}
|
||||
items.add(HomePageList(HomeName, home))
|
||||
}
|
||||
|
||||
if (items.size <= 0) throw ErrorLoadingException()
|
||||
return HomePageResponse(items)
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
val link =
|
||||
""" $mainUrl/graphql?variables={"search":{"allowAdult":false,"allowUnknown":false,"query":"$query"},"limit":26,"page":1,"translationType":"dub","countryOrigin":"ALL"}&extensions={"persistedQuery":{"version":1,"sha256Hash":"d2670e3e27ee109630991152c8484fce5ff5e280c523378001f9a23dc1839068"}}"""
|
||||
var res = app.get(link).text
|
||||
if (res.contains("PERSISTED_QUERY_NOT_FOUND")) {
|
||||
res = app.get(link).text
|
||||
if (res.contains("PERSISTED_QUERY_NOT_FOUND")) return emptyList()
|
||||
}
|
||||
val response = parseJson<AllAnimeQuery>(res)
|
||||
|
||||
val results = response.data.shows.edges.filter {
|
||||
// filtering in case there is an anime with 0 episodes available on the site.
|
||||
!(it.availableEpisodes?.raw == 0 && it.availableEpisodes.sub == 0 && it.availableEpisodes.dub == 0)
|
||||
}
|
||||
|
||||
return results.map {
|
||||
newAnimeSearchResponse(it.name, "$mainUrl/anime/${it.Id}", fix = false) {
|
||||
this.posterUrl = it.thumbnail
|
||||
this.year = it.airedStart?.year
|
||||
this.otherName = it.englishName
|
||||
addDub(it.availableEpisodes?.dub)
|
||||
addSub(it.availableEpisodes?.sub)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class AvailableEpisodesDetail(
|
||||
@JsonProperty("sub") val sub: List<String>,
|
||||
@JsonProperty("dub") val dub: List<String>,
|
||||
@JsonProperty("raw") val raw: List<String>
|
||||
)
|
||||
|
||||
|
||||
override suspend fun load(url: String): LoadResponse? {
|
||||
val rhino = Context.enter()
|
||||
rhino.initStandardObjects()
|
||||
rhino.optimizationLevel = -1
|
||||
val scope: Scriptable = rhino.initStandardObjects()
|
||||
|
||||
val html = app.get(url).text
|
||||
val soup = Jsoup.parse(html)
|
||||
|
||||
val script = soup.select("script").firstOrNull {
|
||||
it.html().contains("window.__NUXT__")
|
||||
} ?: return null
|
||||
|
||||
val js = """
|
||||
const window = {}
|
||||
${script.html()}
|
||||
const returnValue = JSON.stringify(window.__NUXT__.fetch[0].show)
|
||||
""".trimIndent()
|
||||
|
||||
rhino.evaluateString(scope, js, "JavaScript", 1, null)
|
||||
val jsEval = scope.get("returnValue", scope) ?: return null
|
||||
val showData = parseJson<Edges>(jsEval as String)
|
||||
|
||||
val title = showData.name
|
||||
val description = showData.description
|
||||
val poster = showData.thumbnail
|
||||
|
||||
val episodes = showData.availableEpisodes.let {
|
||||
if (it == null) return@let Pair(null, null)
|
||||
Pair(if (it.sub != 0) ((1..it.sub).map { epNum ->
|
||||
Episode(
|
||||
"$mainUrl/anime/${showData.Id}/episodes/sub/$epNum", episode = epNum
|
||||
)
|
||||
}) else null, if (it.dub != 0) ((1..it.dub).map { epNum ->
|
||||
Episode(
|
||||
"$mainUrl/anime/${showData.Id}/episodes/dub/$epNum", episode = epNum
|
||||
)
|
||||
}) else null)
|
||||
}
|
||||
|
||||
val characters = soup.select("div.character > div.card-character-box").mapNotNull {
|
||||
val img = it?.selectFirst("img")?.attr("src") ?: return@mapNotNull null
|
||||
val name = it.selectFirst("div > a")?.ownText() ?: return@mapNotNull null
|
||||
val role = when (it.selectFirst("div > .text-secondary")?.text()?.trim()) {
|
||||
"Main" -> ActorRole.Main
|
||||
"Supporting" -> ActorRole.Supporting
|
||||
"Background" -> ActorRole.Background
|
||||
else -> null
|
||||
}
|
||||
Pair(Actor(name, img), role)
|
||||
}
|
||||
|
||||
// bruh, they use graphql
|
||||
//val recommendations = soup.select("#suggesction > div > div.p > .swipercard")?.mapNotNull {
|
||||
// val recTitle = it?.selectFirst(".showname > a") ?: return@mapNotNull null
|
||||
// val recName = recTitle.text() ?: return@mapNotNull null
|
||||
// val href = fixUrlNull(recTitle.attr("href")) ?: return@mapNotNull null
|
||||
// val img = it.selectFirst(".image > img").attr("src") ?: return@mapNotNull null
|
||||
// AnimeSearchResponse(recName, href, this.name, TvType.Anime, img)
|
||||
//}
|
||||
|
||||
return newAnimeLoadResponse(title, url, TvType.Anime) {
|
||||
posterUrl = poster
|
||||
year = showData.airedStart?.year
|
||||
|
||||
addEpisodes(DubStatus.Subbed, episodes.first)
|
||||
addEpisodes(DubStatus.Dubbed, episodes.second)
|
||||
addActors(characters)
|
||||
//this.recommendations = recommendations
|
||||
|
||||
showStatus = getStatus(showData.status.toString())
|
||||
|
||||
plot = description?.replace(Regex("""<(.*?)>"""), "")
|
||||
}
|
||||
}
|
||||
|
||||
private val embedBlackList = listOf(
|
||||
"https://mp4upload.com/",
|
||||
"https://streamsb.net/",
|
||||
"https://dood.to/",
|
||||
"https://videobin.co/",
|
||||
"https://ok.ru",
|
||||
"https://streamlare.com",
|
||||
)
|
||||
|
||||
private fun embedIsBlacklisted(url: String): Boolean {
|
||||
embedBlackList.forEach {
|
||||
if (it.javaClass.name == "kotlin.text.Regex") {
|
||||
if ((it as Regex).matches(url)) {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
if (url.contains(it)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun String.sanitize(): String {
|
||||
var out = this
|
||||
listOf(Pair("\\u002F", "/")).forEach {
|
||||
out = out.replace(it.first, it.second)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
private data class Links(
|
||||
@JsonProperty("link") val link: String,
|
||||
@JsonProperty("hls") val hls: Boolean?,
|
||||
@JsonProperty("resolutionStr") val resolutionStr: String,
|
||||
@JsonProperty("src") val src: String?
|
||||
)
|
||||
|
||||
private data class AllAnimeVideoApiResponse(
|
||||
@JsonProperty("links") val links: List<Links>
|
||||
)
|
||||
|
||||
private data class ApiEndPoint(
|
||||
@JsonProperty("episodeIframeHead") val episodeIframeHead: String
|
||||
)
|
||||
|
||||
private suspend fun getM3u8Qualities(
|
||||
m3u8Link: String,
|
||||
referer: String,
|
||||
qualityName: String,
|
||||
): List<ExtractorLink> {
|
||||
return M3u8Helper.generateM3u8(
|
||||
this.name,
|
||||
m3u8Link,
|
||||
referer,
|
||||
name = "${this.name} - $qualityName"
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
var apiEndPoint =
|
||||
parseJson<ApiEndPoint>(app.get("$mainUrl/getVersion").text).episodeIframeHead
|
||||
if (apiEndPoint.endsWith("/")) apiEndPoint =
|
||||
apiEndPoint.slice(0 until apiEndPoint.length - 1)
|
||||
|
||||
val html = app.get(data).text
|
||||
|
||||
val sources = Regex("""sourceUrl[:=]"(.+?)"""").findAll(html).toList()
|
||||
.map { URLDecoder.decode(it.destructured.component1().sanitize(), "UTF-8") }
|
||||
sources.apmap {
|
||||
safeApiCall {
|
||||
var link = it.replace(" ", "%20")
|
||||
if (URI(link).isAbsolute || link.startsWith("//")) {
|
||||
if (link.startsWith("//")) link = "https:$it"
|
||||
|
||||
if (Regex("""streaming\.php\?""").matches(link)) {
|
||||
// for now ignore
|
||||
} else if (!embedIsBlacklisted(link)) {
|
||||
if (URI(link).path.contains(".m3u")) {
|
||||
getM3u8Qualities(link, data, URI(link).host).forEach(callback)
|
||||
} else {
|
||||
callback(
|
||||
ExtractorLink(
|
||||
"AllAnime - " + URI(link).host,
|
||||
"",
|
||||
link,
|
||||
data,
|
||||
Qualities.P1080.value,
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
link = apiEndPoint + URI(link).path + ".json?" + URI(link).query
|
||||
val response = app.get(link)
|
||||
|
||||
if (response.code < 400) {
|
||||
val links = parseJson<AllAnimeVideoApiResponse>(response.text).links
|
||||
links.forEach { server ->
|
||||
if (server.hls != null && server.hls) {
|
||||
getM3u8Qualities(
|
||||
server.link,
|
||||
"$apiEndPoint/player?uri=" + (if (URI(server.link).host.isNotEmpty()) server.link else apiEndPoint + URI(
|
||||
server.link
|
||||
).path),
|
||||
server.resolutionStr
|
||||
).forEach(callback)
|
||||
} else {
|
||||
callback(
|
||||
ExtractorLink(
|
||||
"AllAnime - " + URI(server.link).host,
|
||||
server.resolutionStr,
|
||||
server.link,
|
||||
"$apiEndPoint/player?uri=" + (if (URI(server.link).host.isNotEmpty()) server.link else apiEndPoint + URI(
|
||||
server.link
|
||||
).path),
|
||||
Qualities.P1080.value,
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
|
@ -1,215 +0,0 @@
|
|||
package com.lagradost.cloudstream3.animeproviders
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addAniListId
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addDuration
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addMalId
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.M3u8Helper
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
|
||||
class AniPlayProvider : MainAPI() {
|
||||
override var mainUrl = "https://aniplay.it"
|
||||
override var name = "AniPlay"
|
||||
override var lang = "it"
|
||||
override val hasMainPage = true
|
||||
private val dubIdentifier = " (ITA)"
|
||||
|
||||
override val supportedTypes = setOf(
|
||||
TvType.Anime,
|
||||
TvType.AnimeMovie,
|
||||
TvType.OVA
|
||||
)
|
||||
|
||||
companion object {
|
||||
fun getStatus(t: String?): ShowStatus? {
|
||||
return when (t?.lowercase()) {
|
||||
"completato" -> ShowStatus.Completed
|
||||
"in corso" -> ShowStatus.Ongoing
|
||||
else -> null // "annunciato"
|
||||
}
|
||||
}
|
||||
fun getType(t: String?): TvType {
|
||||
return when (t?.lowercase()) {
|
||||
"ona" -> TvType.OVA
|
||||
"movie" -> TvType.AnimeMovie
|
||||
else -> TvType.Anime //"serie", "special"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isDub(title: String): Boolean{
|
||||
return title.contains(dubIdentifier)
|
||||
}
|
||||
|
||||
data class ApiPoster(
|
||||
@JsonProperty("imageFull") val posterUrl: String
|
||||
)
|
||||
|
||||
data class ApiMainPageAnime(
|
||||
@JsonProperty("animeId") val id: Int,
|
||||
@JsonProperty("episodeNumber") val episode: String?,
|
||||
@JsonProperty("animeTitle") val title: String,
|
||||
@JsonProperty("animeType") val type: String,
|
||||
@JsonProperty("fullHd") val fullHD: Boolean,
|
||||
@JsonProperty("animeVerticalImages") val posters: List<ApiPoster>
|
||||
)
|
||||
|
||||
data class ApiSearchResult(
|
||||
@JsonProperty("id") val id: Int,
|
||||
@JsonProperty("title") val title: String,
|
||||
@JsonProperty("status") val status: String,
|
||||
@JsonProperty("type") val type: String,
|
||||
@JsonProperty("verticalImages") val posters: List<ApiPoster>
|
||||
)
|
||||
|
||||
data class ApiGenres(
|
||||
@JsonProperty("description") val name: String
|
||||
)
|
||||
data class ApiWebsite(
|
||||
@JsonProperty("listWebsiteId") val websiteId: Int,
|
||||
@JsonProperty("url") val url: String
|
||||
)
|
||||
|
||||
data class ApiEpisode(
|
||||
@JsonProperty("id") val id: Int,
|
||||
@JsonProperty("title") val title: String?,
|
||||
@JsonProperty("episodeNumber") val number: String,
|
||||
)
|
||||
|
||||
private fun ApiEpisode.toEpisode() : Episode? {
|
||||
val number = this.number.toIntOrNull() ?: return null
|
||||
return Episode(
|
||||
data = "$mainUrl/api/episode/${this.id}",
|
||||
episode = number,
|
||||
name = this.title
|
||||
)
|
||||
}
|
||||
|
||||
data class ApiSeason(
|
||||
@JsonProperty("id") val id: Int,
|
||||
@JsonProperty("name") val name: String
|
||||
)
|
||||
|
||||
private suspend fun ApiSeason.toEpisodeList(url: String) : List<Episode> {
|
||||
return app.get("$url/season/${this.id}").parsed<List<ApiEpisode>>().mapNotNull { it.toEpisode() }
|
||||
}
|
||||
|
||||
data class ApiAnime(
|
||||
@JsonProperty("title") val title: String,
|
||||
@JsonProperty("alternativeTitle") val japTitle: String?,
|
||||
@JsonProperty("episodeDuration") val duration: Int,
|
||||
@JsonProperty("storyline") val plot: String,
|
||||
@JsonProperty("type") val type: String,
|
||||
@JsonProperty("status") val status: String,
|
||||
@JsonProperty("genres") val genres: List<ApiGenres>,
|
||||
@JsonProperty("verticalImages") val posters: List<ApiPoster>,
|
||||
@JsonProperty("listWebsites") val websites: List<ApiWebsite>,
|
||||
@JsonProperty("episodes") val episodes: List<ApiEpisode>,
|
||||
@JsonProperty("seasons") val seasons: List<ApiSeason>?
|
||||
)
|
||||
|
||||
data class ApiEpisodeUrl(
|
||||
@JsonProperty("videoUrl") val url: String
|
||||
)
|
||||
|
||||
override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse {
|
||||
val response = app.get("$mainUrl/api/home/latest-episodes?page=0").parsed<List<ApiMainPageAnime>>()
|
||||
|
||||
val results = response.map{
|
||||
val isDub = isDub(it.title)
|
||||
newAnimeSearchResponse(
|
||||
name = if (isDub) it.title.replace(dubIdentifier, "") else it.title,
|
||||
url = "$mainUrl/api/anime/${it.id}",
|
||||
type = getType(it.type),
|
||||
){
|
||||
addDubStatus(isDub, it.episode?.toIntOrNull())
|
||||
this.posterUrl = it.posters.first().posterUrl
|
||||
this.quality = if (it.fullHD) SearchQuality.HD else null
|
||||
}
|
||||
}
|
||||
return HomePageResponse(listOf(HomePageList("Ultime uscite",results)))
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
val response = app.get("$mainUrl/api/anime/advanced-search?page=0&size=36&query=$query").parsed<List<ApiSearchResult>>()
|
||||
|
||||
return response.map {
|
||||
val isDub = isDub(it.title)
|
||||
|
||||
newAnimeSearchResponse(
|
||||
name = if (isDub) it.title.replace(dubIdentifier, "") else it.title,
|
||||
url = "$mainUrl/api/anime/${it.id}",
|
||||
type = getType(it.type),
|
||||
){
|
||||
addDubStatus(isDub)
|
||||
this.posterUrl = it.posters.first().posterUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
|
||||
val response = app.get(url).parsed<ApiAnime>()
|
||||
|
||||
val tags: List<String> = response.genres.map { it.name }
|
||||
|
||||
val malId: Int? = response.websites.find { it.websiteId == 1 }?.url?.removePrefix("https://myanimelist.net/anime/")?.split("/")?.first()?.toIntOrNull()
|
||||
val aniListId: Int? = response.websites.find { it.websiteId == 4 }?.url?.removePrefix("https://anilist.co/anime/")?.split("/")?.first()?.toIntOrNull()
|
||||
|
||||
val episodes = if (response.seasons.isNullOrEmpty()) response.episodes.mapNotNull { it.toEpisode() } else response.seasons.map{ it.toEpisodeList(url) }.flatten()
|
||||
val isDub = isDub(response.title)
|
||||
|
||||
return newAnimeLoadResponse(response.title, url, getType(response.type)) {
|
||||
this.name = if (isDub) response.title.replace(dubIdentifier, "") else response.title
|
||||
this.japName = response.japTitle
|
||||
this.plot = response.plot
|
||||
this.tags = tags
|
||||
this.showStatus = getStatus(response.status)
|
||||
addPoster(response.posters.first().posterUrl)
|
||||
addEpisodes(if (isDub) DubStatus.Dubbed else DubStatus.Subbed, episodes)
|
||||
addMalId(malId)
|
||||
addAniListId(aniListId)
|
||||
addDuration(response.duration.toString())
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
|
||||
val episode = app.get(data).parsed<ApiEpisodeUrl>()
|
||||
|
||||
if(episode.url.contains(".m3u8")){
|
||||
val m3u8Helper = M3u8Helper()
|
||||
val streams = m3u8Helper.m3u8Generation(M3u8Helper.M3u8Stream(episode.url,Qualities.Unknown.value), false)
|
||||
|
||||
streams.forEach {
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
name,
|
||||
name,
|
||||
it.streamUrl,
|
||||
referer = mainUrl,
|
||||
quality = it.quality ?: Qualities.Unknown.value,
|
||||
isM3u8 = it.streamUrl.contains(".m3u8"))) }
|
||||
return true
|
||||
}
|
||||
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
name,
|
||||
name,
|
||||
episode.url,
|
||||
referer = mainUrl,
|
||||
quality = Qualities.Unknown.value,
|
||||
isM3u8 = false,
|
||||
)
|
||||
)
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -1,274 +0,0 @@
|
|||
package com.lagradost.cloudstream3.animeproviders
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addAniListId
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||
import java.net.URLDecoder
|
||||
|
||||
class AniflixProvider : MainAPI() {
|
||||
override var mainUrl = "https://aniflix.pro"
|
||||
override var name = "Aniflix"
|
||||
override val hasMainPage = true
|
||||
|
||||
override val supportedTypes = setOf(
|
||||
TvType.AnimeMovie,
|
||||
TvType.OVA,
|
||||
TvType.Anime,
|
||||
)
|
||||
|
||||
companion object {
|
||||
var token: String? = null
|
||||
}
|
||||
|
||||
private suspend fun getToken(): String {
|
||||
return token ?: run {
|
||||
Regex("([^/]*)/_buildManifest\\.js").find(app.get(mainUrl).text)?.groupValues?.getOrNull(
|
||||
1
|
||||
)
|
||||
?.also {
|
||||
token = it
|
||||
}
|
||||
?: throw ErrorLoadingException("No token found")
|
||||
}
|
||||
}
|
||||
|
||||
private fun Anime.toSearchResponse(): SearchResponse? {
|
||||
return newAnimeSearchResponse(
|
||||
title?.english ?: title?.romaji ?: return null,
|
||||
"$mainUrl/anime/${id ?: return null}"
|
||||
) {
|
||||
posterUrl = coverImage?.large ?: coverImage?.medium
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse {
|
||||
val items = ArrayList<HomePageList>()
|
||||
val soup = app.get(mainUrl).document
|
||||
val elements = listOf(
|
||||
Pair("Trending Now", "div:nth-child(3) > div a"),
|
||||
Pair("Popular", "div:nth-child(4) > div a"),
|
||||
Pair("Top Rated", "div:nth-child(5) > div a"),
|
||||
)
|
||||
|
||||
elements.map { (name, element) ->
|
||||
val home = soup.select(element).map {
|
||||
val href = it.attr("href")
|
||||
val title = it.selectFirst("p.mt-2")!!.text()
|
||||
val image = it.selectFirst("img.rounded-md[sizes]")!!.attr("src").replace("/_next/image?url=","")
|
||||
.replace(Regex("\\&.*\$"),"")
|
||||
val realposter = URLDecoder.decode(image, "UTF-8")
|
||||
newAnimeSearchResponse(title, fixUrl(href)) {
|
||||
this.posterUrl = realposter
|
||||
}
|
||||
}
|
||||
items.add(HomePageList(name, home))
|
||||
}
|
||||
|
||||
return HomePageResponse(items)
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse>? {
|
||||
val token = getToken()
|
||||
val url = "$mainUrl/_next/data/$token/search.json?keyword=$query"
|
||||
val response = app.get(url)
|
||||
val searchResponse =
|
||||
response.parsedSafe<Search>()
|
||||
?: throw ErrorLoadingException("No Media")
|
||||
return searchResponse.pageProps?.searchResults?.Page?.media?.mapNotNull { media ->
|
||||
media.toSearchResponse()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val token = getToken()
|
||||
|
||||
val id = Regex("$mainUrl/anime/([0-9]*)").find(url)?.groupValues?.getOrNull(1)
|
||||
?: throw ErrorLoadingException("Error parsing link for id")
|
||||
|
||||
val res = app.get("https://aniflix.pro/_next/data/$token/anime/$id.json?id=$id")
|
||||
.parsedSafe<AnimeResponsePage>()?.pageProps
|
||||
?: throw ErrorLoadingException("Invalid Json reponse")
|
||||
val isMovie = res.anime.format == "MOVIE"
|
||||
return newAnimeLoadResponse(
|
||||
res.anime.title?.english ?: res.anime.title?.romaji
|
||||
?: throw ErrorLoadingException("Invalid title reponse"),
|
||||
url, if (isMovie) TvType.AnimeMovie else TvType.Anime
|
||||
) {
|
||||
recommendations = res.recommended.mapNotNull { it.toSearchResponse() }
|
||||
tags = res.anime.genres
|
||||
posterUrl = res.anime.coverImage?.large ?: res.anime.coverImage?.medium
|
||||
plot = res.anime.description
|
||||
showStatus = when (res.anime.status) {
|
||||
"FINISHED" -> ShowStatus.Completed
|
||||
"RELEASING" -> ShowStatus.Ongoing
|
||||
else -> null
|
||||
}
|
||||
addAniListId(id.toIntOrNull())
|
||||
|
||||
// subbed because they are both subbed and dubbed
|
||||
if (isMovie)
|
||||
addEpisodes(
|
||||
DubStatus.Subbed,
|
||||
listOf(newEpisode("$mainUrl/api/anime/?id=$id&episode=1"))
|
||||
)
|
||||
else
|
||||
addEpisodes(DubStatus.Subbed, res.episodes.episodes?.nodes?.mapIndexed { index, node ->
|
||||
val episodeIndex = node?.number ?: (index + 1)
|
||||
//"$mainUrl/_next/data/$token/watch/$id.json?episode=${node.number ?: return@mapNotNull null}&id=$id"
|
||||
newEpisode("$mainUrl/api/anime?id=$id&episode=${episodeIndex}") {
|
||||
episode = episodeIndex
|
||||
posterUrl = node?.thumbnail?.original?.url
|
||||
name = node?.titles?.canonical
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
return app.get(data).parsed<AniLoadResponse>().let { res ->
|
||||
val dubReferer = res.dub?.Referer ?: ""
|
||||
res.dub?.sources?.forEach { source ->
|
||||
callback(
|
||||
ExtractorLink(
|
||||
name,
|
||||
"${source.label ?: name} (DUB)",
|
||||
source.file ?: return@forEach,
|
||||
dubReferer,
|
||||
getQualityFromName(source.label),
|
||||
source.type == "hls"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val subReferer = res.dub?.Referer ?: ""
|
||||
res.sub?.sources?.forEach { source ->
|
||||
callback(
|
||||
ExtractorLink(
|
||||
name,
|
||||
"${source.label ?: name} (SUB)",
|
||||
source.file ?: return@forEach,
|
||||
subReferer,
|
||||
getQualityFromName(source.label),
|
||||
source.type == "hls"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
!res.dub?.sources.isNullOrEmpty() && !res.sub?.sources.isNullOrEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
data class AniLoadResponse(
|
||||
@JsonProperty("sub") val sub: DubSubSource?,
|
||||
@JsonProperty("dub") val dub: DubSubSource?,
|
||||
@JsonProperty("episodes") val episodes: Int?
|
||||
)
|
||||
|
||||
data class Sources(
|
||||
@JsonProperty("file") val file: String?,
|
||||
@JsonProperty("label") val label: String?,
|
||||
@JsonProperty("type") val type: String?
|
||||
)
|
||||
|
||||
data class DubSubSource(
|
||||
@JsonProperty("Referer") var Referer: String?,
|
||||
@JsonProperty("sources") var sources: ArrayList<Sources> = arrayListOf()
|
||||
)
|
||||
|
||||
data class PageProps(
|
||||
@JsonProperty("searchResults") val searchResults: SearchResults?
|
||||
)
|
||||
|
||||
data class SearchResults(
|
||||
@JsonProperty("Page") val Page: Page?
|
||||
)
|
||||
|
||||
data class Page(
|
||||
@JsonProperty("media") val media: ArrayList<Anime> = arrayListOf()
|
||||
)
|
||||
|
||||
data class CoverImage(
|
||||
@JsonProperty("color") val color: String?,
|
||||
@JsonProperty("medium") val medium: String?,
|
||||
@JsonProperty("large") val large: String?,
|
||||
)
|
||||
|
||||
data class Title(
|
||||
@JsonProperty("english") val english: String?,
|
||||
@JsonProperty("romaji") val romaji: String?,
|
||||
)
|
||||
|
||||
data class Search(
|
||||
@JsonProperty("pageProps") val pageProps: PageProps?,
|
||||
@JsonProperty("__N_SSP") val _NSSP: Boolean?
|
||||
)
|
||||
|
||||
data class Anime(
|
||||
@JsonProperty("status") val status: String?,
|
||||
@JsonProperty("id") val id: Int?,
|
||||
@JsonProperty("title") val title: Title?,
|
||||
@JsonProperty("coverImage") val coverImage: CoverImage?,
|
||||
@JsonProperty("format") val format: String?,
|
||||
@JsonProperty("duration") val duration: Int?,
|
||||
@JsonProperty("meanScore") val meanScore: Int?,
|
||||
@JsonProperty("nextAiringEpisode") val nextAiringEpisode: String?,
|
||||
@JsonProperty("bannerImage") val bannerImage: String?,
|
||||
@JsonProperty("description") val description: String?,
|
||||
@JsonProperty("genres") val genres: ArrayList<String>? = null,
|
||||
@JsonProperty("season") val season: String?,
|
||||
@JsonProperty("startDate") val startDate: StartDate?,
|
||||
)
|
||||
|
||||
data class StartDate(
|
||||
@JsonProperty("year") val year: Int?
|
||||
)
|
||||
|
||||
data class AnimeResponsePage(
|
||||
@JsonProperty("pageProps") val pageProps: AnimeResponse?,
|
||||
)
|
||||
|
||||
data class AnimeResponse(
|
||||
@JsonProperty("anime") val anime: Anime,
|
||||
@JsonProperty("recommended") val recommended: ArrayList<Anime>,
|
||||
@JsonProperty("episodes") val episodes: EpisodesParent,
|
||||
)
|
||||
|
||||
data class EpisodesParent(
|
||||
@JsonProperty("id") val id: String?,
|
||||
@JsonProperty("season") val season: String?,
|
||||
@JsonProperty("startDate") val startDate: String?,
|
||||
@JsonProperty("episodeCount") val episodeCount: Int?,
|
||||
@JsonProperty("episodes") val episodes: Episodes?,
|
||||
)
|
||||
|
||||
data class Episodes(
|
||||
@JsonProperty("nodes") val nodes: ArrayList<Nodes?> = arrayListOf()
|
||||
)
|
||||
|
||||
data class Nodes(
|
||||
@JsonProperty("number") val number: Int? = null,
|
||||
@JsonProperty("titles") val titles: Titles?,
|
||||
@JsonProperty("thumbnail") val thumbnail: Thumbnail?,
|
||||
)
|
||||
|
||||
data class Titles(
|
||||
@JsonProperty("canonical") val canonical: String?,
|
||||
)
|
||||
|
||||
data class Original(
|
||||
@JsonProperty("url") val url: String?,
|
||||
)
|
||||
|
||||
data class Thumbnail(
|
||||
@JsonProperty("original") val original: Original?,
|
||||
)
|
||||
}
|
|
@ -1,119 +0,0 @@
|
|||
package com.lagradost.cloudstream3.animeproviders
|
||||
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
import com.lagradost.cloudstream3.utils.extractorApis
|
||||
import org.jsoup.Jsoup
|
||||
import java.util.*
|
||||
|
||||
class AnimeFlickProvider : MainAPI() {
|
||||
companion object {
|
||||
fun getType(t: String): TvType {
|
||||
return if (t.contains("OVA") || t.contains("Special")) TvType.OVA
|
||||
else if (t.contains("Movie")) TvType.AnimeMovie
|
||||
else TvType.Anime
|
||||
}
|
||||
}
|
||||
|
||||
override var mainUrl = "https://animeflick.net"
|
||||
override var name = "AnimeFlick"
|
||||
override val hasQuickSearch = false
|
||||
override val hasMainPage = false
|
||||
|
||||
override val supportedTypes = setOf(
|
||||
TvType.AnimeMovie,
|
||||
TvType.Anime,
|
||||
TvType.OVA
|
||||
)
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
val link = "https://animeflick.net/search.php?search=$query"
|
||||
val html = app.get(link).text
|
||||
val doc = Jsoup.parse(html)
|
||||
|
||||
return doc.select(".row.mt-2").mapNotNull {
|
||||
val href = mainUrl + it.selectFirst("a")?.attr("href")
|
||||
val title = it.selectFirst("h5 > a")?.text() ?: return@mapNotNull null
|
||||
val poster = mainUrl + it.selectFirst("img")?.attr("src")?.replace("70x110", "225x320")
|
||||
AnimeSearchResponse(
|
||||
title,
|
||||
href,
|
||||
this.name,
|
||||
getType(title),
|
||||
poster,
|
||||
null,
|
||||
EnumSet.of(DubStatus.Subbed),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val html = app.get(url).text
|
||||
val doc = Jsoup.parse(html)
|
||||
|
||||
val poster = mainUrl + doc.selectFirst("img.rounded")?.attr("src")
|
||||
val title = doc.selectFirst("h2.title")!!.text()
|
||||
|
||||
val yearText = doc.selectFirst(".trending-year")?.text()
|
||||
val year =
|
||||
if (yearText != null) Regex("""(\d{4})""").find(yearText)?.destructured?.component1()
|
||||
?.toIntOrNull() else null
|
||||
val description = doc.selectFirst("p")?.text()
|
||||
|
||||
val genres = doc.select("a[href*=\"genre-\"]").map { it.text() }
|
||||
|
||||
val episodes = doc.select("#collapseOne .block-space > .row > div:nth-child(2)").map {
|
||||
val name = it.selectFirst("a")?.text()
|
||||
val link = mainUrl + it.selectFirst("a")?.attr("href")
|
||||
Episode(link, name)
|
||||
}.reversed()
|
||||
|
||||
return newAnimeLoadResponse(title, url, getType(title)) {
|
||||
posterUrl = poster
|
||||
this.year = year
|
||||
|
||||
addEpisodes(DubStatus.Subbed, episodes)
|
||||
|
||||
plot = description
|
||||
tags = genres
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
val html = app.get(data).text
|
||||
|
||||
val episodeRegex = Regex("""(https://.*?\.mp4)""")
|
||||
val links = episodeRegex.findAll(html).map {
|
||||
it.value
|
||||
}.toList()
|
||||
for (link in links) {
|
||||
var alreadyAdded = false
|
||||
for (extractor in extractorApis) {
|
||||
if (link.startsWith(extractor.mainUrl)) {
|
||||
extractor.getSafeUrl(link, data, subtitleCallback, callback)
|
||||
alreadyAdded = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!alreadyAdded) {
|
||||
callback(
|
||||
ExtractorLink(
|
||||
this.name,
|
||||
"${this.name} - Auto",
|
||||
link,
|
||||
"",
|
||||
Qualities.P1080.value
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -1,192 +0,0 @@
|
|||
package com.lagradost.cloudstream3.animeproviders
|
||||
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.APIHolder.getCaptchaToken
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
import com.lagradost.nicehttp.NiceResponse
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Element
|
||||
|
||||
class AnimeIndoProvider : MainAPI() {
|
||||
override var mainUrl = "https://animeindo.sbs"
|
||||
override var name = "AnimeIndo"
|
||||
override val hasMainPage = true
|
||||
override var lang = "id"
|
||||
override val hasDownloadSupport = true
|
||||
|
||||
override val supportedTypes = setOf(
|
||||
TvType.Anime,
|
||||
TvType.AnimeMovie,
|
||||
TvType.OVA
|
||||
)
|
||||
|
||||
companion object {
|
||||
fun getType(t: String): TvType {
|
||||
return if (t.contains("OVA") || t.contains("Special")) TvType.OVA
|
||||
else if (t.contains("Movie")) TvType.AnimeMovie
|
||||
else TvType.Anime
|
||||
}
|
||||
|
||||
fun getStatus(t: String): ShowStatus {
|
||||
return when (t) {
|
||||
"Finished Airing" -> ShowStatus.Completed
|
||||
"Currently Airing" -> ShowStatus.Ongoing
|
||||
else -> ShowStatus.Completed
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun request(url: String): NiceResponse {
|
||||
val req = app.get(
|
||||
url,
|
||||
cookies = mapOf("recaptcha_cookie" to "#Asia/Jakarta#-420#win32#Windows#0,false,false#Google Inc. (Intel)~ANGLE (Intel, Intel(R) HD Graphics 400 Direct3D11 vs_5_0 ps_5_0)")
|
||||
)
|
||||
if (req.isSuccessful) {
|
||||
return req
|
||||
} else {
|
||||
val document = app.get(url).document
|
||||
val captchaKey =
|
||||
document.select("script[src*=https://www.google.com/recaptcha/api.js?render=]")
|
||||
.attr("src").substringAfter("render=").substringBefore("&")
|
||||
val token = getCaptchaToken(url, captchaKey)
|
||||
return app.post(
|
||||
url,
|
||||
data = mapOf(
|
||||
"action" to "recaptcha_for_all",
|
||||
"token" to "$token",
|
||||
"sitekey" to captchaKey
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val mainPage = mainPageOf(
|
||||
"$mainUrl/anime-terbaru/page/" to "Anime Terbaru",
|
||||
"$mainUrl/donghua-terbaru/page/" to "Donghua Terbaru"
|
||||
)
|
||||
|
||||
override suspend fun getMainPage(
|
||||
page: Int,
|
||||
request: MainPageRequest
|
||||
): HomePageResponse {
|
||||
val document = request(request.data + page).document
|
||||
val home = document.select("div.post-show > article").mapNotNull {
|
||||
it.toSearchResult()
|
||||
}
|
||||
return newHomePageResponse(request.name, home)
|
||||
}
|
||||
|
||||
private fun getProperAnimeLink(uri: String): String {
|
||||
return if (uri.contains("/anime/")) {
|
||||
uri
|
||||
} else {
|
||||
var title = uri.substringAfter("$mainUrl/")
|
||||
title = when {
|
||||
(title.contains("-episode")) && !(title.contains("-movie")) -> Regex("(.+)-episode").find(
|
||||
title
|
||||
)?.groupValues?.get(1).toString()
|
||||
(title.contains("-movie")) -> Regex("(.+)-movie").find(title)?.groupValues?.get(
|
||||
1
|
||||
).toString()
|
||||
else -> title
|
||||
}
|
||||
"$mainUrl/anime/$title"
|
||||
}
|
||||
}
|
||||
|
||||
private fun Element.toSearchResult(): AnimeSearchResponse? {
|
||||
val title = this.selectFirst("div.title")?.text()?.trim() ?: return null
|
||||
val href = getProperAnimeLink(this.selectFirst("a")!!.attr("href"))
|
||||
val posterUrl = this.select("img[itemprop=image]").attr("src").toString()
|
||||
val type = getType(this.select("div.type").text().trim())
|
||||
val epNum =
|
||||
this.selectFirst("span.episode")?.ownText()?.replace(Regex("[^0-9]"), "")?.trim()
|
||||
?.toIntOrNull()
|
||||
return newAnimeSearchResponse(title, href, type) {
|
||||
this.posterUrl = posterUrl
|
||||
addSub(epNum)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
val link = "$mainUrl/?s=$query"
|
||||
val document = request(link).document
|
||||
|
||||
return document.select(".site-main.relat > article").map {
|
||||
val title = it.selectFirst("div.title > h2")!!.ownText().trim()
|
||||
val href = it.selectFirst("a")!!.attr("href")
|
||||
val posterUrl = it.selectFirst("img")!!.attr("src").toString()
|
||||
val type = getType(it.select("div.type").text().trim())
|
||||
newAnimeSearchResponse(title, href, type) {
|
||||
this.posterUrl = posterUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val document = request(url).document
|
||||
|
||||
val title = document.selectFirst("h1.entry-title")?.text().toString().trim()
|
||||
val poster = document.selectFirst("div.thumb > img[itemprop=image]")?.attr("src")
|
||||
val tags = document.select("div.genxed > a").map { it.text() }
|
||||
val type = getType(
|
||||
document.selectFirst("div.info-content > div.spe > span:nth-child(6)")?.ownText()
|
||||
.toString()
|
||||
)
|
||||
val year = Regex("\\d, ([0-9]*)").find(
|
||||
document.select("div.info-content > div.spe > span:nth-child(9) > time").text()
|
||||
)?.groupValues?.get(1)?.toIntOrNull()
|
||||
val status = getStatus(
|
||||
document.selectFirst("div.info-content > div.spe > span:nth-child(1)")!!.ownText()
|
||||
.trim()
|
||||
)
|
||||
val description = document.select("div[itemprop=description] > p").text()
|
||||
val trailer = document.selectFirst("div.player-embed iframe")?.attr("src")
|
||||
val episodes = document.select("div.lstepsiode.listeps ul li").mapNotNull {
|
||||
val header = it.selectFirst("span.lchx > a") ?: return@mapNotNull null
|
||||
val name = header.text().trim()
|
||||
val episode = header.text().trim().replace("Episode", "").trim().toIntOrNull()
|
||||
val link = fixUrl(header.attr("href"))
|
||||
Episode(link, name = name, episode = episode)
|
||||
}.reversed()
|
||||
|
||||
return newAnimeLoadResponse(title, url, type) {
|
||||
engName = title
|
||||
posterUrl = poster
|
||||
this.year = year
|
||||
addEpisodes(DubStatus.Subbed, episodes)
|
||||
showStatus = status
|
||||
plot = description
|
||||
this.tags = tags
|
||||
addTrailer(trailer)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
|
||||
val document = request(data).document
|
||||
document.select("div.itemleft > .mirror > option").mapNotNull {
|
||||
fixUrl(Jsoup.parse(base64Decode(it.attr("value"))).select("iframe").attr("src"))
|
||||
}.apmap {
|
||||
if (it.startsWith("https://uservideo.xyz")) {
|
||||
app.get(it, referer = "$mainUrl/").document.select("iframe").attr("src")
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}.apmap {
|
||||
loadExtractor(it, data, subtitleCallback, callback)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,562 +0,0 @@
|
|||
package com.lagradost.cloudstream3.animeproviders
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addAniListId
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addMalId
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.JsUnpacker
|
||||
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||
import com.lagradost.nicehttp.NiceResponse
|
||||
import org.jsoup.Jsoup
|
||||
import kotlin.math.pow
|
||||
|
||||
class AnimePaheProvider : MainAPI() {
|
||||
// credit to https://github.com/justfoolingaround/animdl/tree/master/animdl/core/codebase/providers/animepahe
|
||||
companion object {
|
||||
const val MAIN_URL = "https://animepahe.com"
|
||||
|
||||
var cookies: Map<String, String> = mapOf()
|
||||
private fun getType(t: String): TvType {
|
||||
return if (t.contains("OVA") || t.contains("Special")) TvType.OVA
|
||||
else if (t.contains("Movie")) TvType.AnimeMovie
|
||||
else TvType.Anime
|
||||
}
|
||||
|
||||
suspend fun generateSession(): Boolean {
|
||||
if (cookies.isNotEmpty()) return true
|
||||
return try {
|
||||
val response = app.get("$MAIN_URL/")
|
||||
cookies = response.cookies
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
val YTSM = Regex("ysmm = '([^']+)")
|
||||
|
||||
val KWIK_PARAMS_RE = Regex("""\("(\w+)",\d+,"(\w+)",(\d+),(\d+),\d+\)""")
|
||||
val KWIK_D_URL = Regex("action=\"([^\"]+)\"")
|
||||
val KWIK_D_TOKEN = Regex("value=\"([^\"]+)\"")
|
||||
val YOUTUBE_VIDEO_LINK =
|
||||
Regex("""(^(?:https?:)?(?://)?(?:www\.)?(?:youtu\.be/|youtube(?:-nocookie)?\.(?:[A-Za-z]{2,4}|[A-Za-z]{2,3}\.[A-Za-z]{2})/)(?:watch|embed/|vi?/)*(?:\?[\w=&]*vi?=)?[^#&?/]{11}.*${'$'})""")
|
||||
}
|
||||
|
||||
override var mainUrl = MAIN_URL
|
||||
override var name = "AnimePahe"
|
||||
override val hasQuickSearch = false
|
||||
override val hasMainPage = true
|
||||
|
||||
override val supportedTypes = setOf(
|
||||
TvType.AnimeMovie,
|
||||
TvType.Anime,
|
||||
TvType.OVA
|
||||
)
|
||||
|
||||
override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse {
|
||||
data class Data(
|
||||
@JsonProperty("id") val id: Int,
|
||||
@JsonProperty("anime_id") val animeId: Int,
|
||||
@JsonProperty("anime_title") val animeTitle: String,
|
||||
@JsonProperty("anime_slug") val animeSlug: String,
|
||||
@JsonProperty("episode") val episode: Int,
|
||||
@JsonProperty("snapshot") val snapshot: String,
|
||||
@JsonProperty("created_at") val createdAt: String,
|
||||
@JsonProperty("anime_session") val animeSession: String,
|
||||
)
|
||||
|
||||
data class AnimePaheLatestReleases(
|
||||
@JsonProperty("total") val total: Int,
|
||||
@JsonProperty("data") val data: List<Data>
|
||||
)
|
||||
|
||||
val urls = listOf(
|
||||
Pair("$mainUrl/api?m=airing&page=1", "Latest Releases"),
|
||||
)
|
||||
|
||||
val items = ArrayList<HomePageList>()
|
||||
for (i in urls) {
|
||||
try {
|
||||
val response = app.get(i.first).text
|
||||
val episodes = parseJson<AnimePaheLatestReleases>(response).data.map {
|
||||
newAnimeSearchResponse(
|
||||
it.animeTitle,
|
||||
"https://pahe.win/a/${it.animeId}?slug=${it.animeTitle}",
|
||||
fix = false
|
||||
) {
|
||||
this.posterUrl = it.snapshot
|
||||
addDubStatus(DubStatus.Subbed, it.episode)
|
||||
}
|
||||
}
|
||||
|
||||
items.add(HomePageList(i.second, episodes))
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
if (items.size <= 0) throw ErrorLoadingException()
|
||||
return HomePageResponse(items)
|
||||
}
|
||||
|
||||
data class AnimePaheSearchData(
|
||||
@JsonProperty("id") val id: Int,
|
||||
@JsonProperty("slug") val slug: String,
|
||||
@JsonProperty("title") val title: String,
|
||||
@JsonProperty("type") val type: String,
|
||||
@JsonProperty("episodes") val episodes: Int,
|
||||
@JsonProperty("status") val status: String,
|
||||
@JsonProperty("season") val season: String,
|
||||
@JsonProperty("year") val year: Int,
|
||||
@JsonProperty("score") val score: Double,
|
||||
@JsonProperty("poster") val poster: String,
|
||||
@JsonProperty("session") val session: String,
|
||||
@JsonProperty("relevance") val relevance: String
|
||||
)
|
||||
|
||||
data class AnimePaheSearch(
|
||||
@JsonProperty("total") val total: Int,
|
||||
@JsonProperty("data") val data: List<AnimePaheSearchData>
|
||||
)
|
||||
|
||||
private suspend fun getAnimeByIdAndTitle(title: String, animeId: Int): String? {
|
||||
val url = "$mainUrl/api?m=search&l=8&q=$title"
|
||||
val headers = mapOf("referer" to "$mainUrl/")
|
||||
|
||||
val req = app.get(url, headers = headers).text
|
||||
val data = parseJson<AnimePaheSearch>(req)
|
||||
for (anime in data.data) {
|
||||
if (anime.id == animeId) {
|
||||
return "https://animepahe.com/anime/${anime.session}"
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
val url = "$mainUrl/api?m=search&l=8&q=$query"
|
||||
val headers = mapOf("referer" to "$mainUrl/")
|
||||
|
||||
val req = app.get(url, headers = headers).text
|
||||
val data = parseJson<AnimePaheSearch>(req)
|
||||
|
||||
return data.data.map {
|
||||
newAnimeSearchResponse(
|
||||
it.title,
|
||||
"https://pahe.win/a/${it.id}?slug=${it.title}",
|
||||
fix = false
|
||||
) {
|
||||
this.posterUrl = it.poster
|
||||
addDubStatus(DubStatus.Subbed, it.episodes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class AnimeData(
|
||||
@JsonProperty("id") val id: Int,
|
||||
@JsonProperty("anime_id") val animeId: Int,
|
||||
@JsonProperty("episode") val episode: Int,
|
||||
@JsonProperty("title") val title: String,
|
||||
@JsonProperty("snapshot") val snapshot: String,
|
||||
@JsonProperty("session") val session: String,
|
||||
@JsonProperty("filler") val filler: Int,
|
||||
@JsonProperty("created_at") val createdAt: String
|
||||
)
|
||||
|
||||
private data class AnimePaheAnimeData(
|
||||
@JsonProperty("total") val total: Int,
|
||||
@JsonProperty("per_page") val perPage: Int,
|
||||
@JsonProperty("current_page") val currentPage: Int,
|
||||
@JsonProperty("last_page") val lastPage: Int,
|
||||
@JsonProperty("next_page_url") val nextPageUrl: String?,
|
||||
@JsonProperty("prev_page_url") val prevPageUrl: String?,
|
||||
@JsonProperty("from") val from: Int,
|
||||
@JsonProperty("to") val to: Int,
|
||||
@JsonProperty("data") val data: List<AnimeData>
|
||||
)
|
||||
|
||||
private suspend fun generateListOfEpisodes(link: String): ArrayList<Episode> {
|
||||
try {
|
||||
val attrs = link.split('/')
|
||||
val id = attrs[attrs.size - 1].split("?")[0]
|
||||
|
||||
val uri = "$mainUrl/api?m=release&id=$id&sort=episode_asc&page=1"
|
||||
val headers = mapOf("referer" to "$mainUrl/")
|
||||
|
||||
val req = app.get(uri, headers = headers).text
|
||||
val data = parseJson<AnimePaheAnimeData>(req)
|
||||
|
||||
val lastPage = data.lastPage
|
||||
val perPage = data.perPage
|
||||
val total = data.total
|
||||
var ep = 1
|
||||
val episodes = ArrayList<Episode>()
|
||||
|
||||
fun getEpisodeTitle(k: AnimeData): String {
|
||||
return k.title.ifEmpty {
|
||||
"Episode ${k.episode}"
|
||||
}
|
||||
}
|
||||
|
||||
if (lastPage == 1 && perPage > total) {
|
||||
data.data.forEach {
|
||||
episodes.add(
|
||||
newEpisode("$mainUrl/api?m=links&id=${it.animeId}&session=${it.session}&p=kwik!!TRUE!!") {
|
||||
addDate(it.createdAt)
|
||||
this.name = getEpisodeTitle(it)
|
||||
this.posterUrl = it.snapshot
|
||||
}
|
||||
)
|
||||
}
|
||||
} else {
|
||||
for (page in 0 until lastPage) {
|
||||
for (i in 0 until perPage) {
|
||||
if (ep <= total) {
|
||||
episodes.add(
|
||||
Episode(
|
||||
"$mainUrl/api?m=release&id=${id}&sort=episode_asc&page=${page + 1}&ep=${ep}!!FALSE!!"
|
||||
)
|
||||
)
|
||||
++ep
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return episodes
|
||||
} catch (e: Exception) {
|
||||
return ArrayList()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse? {
|
||||
return suspendSafeApiCall {
|
||||
val regex = Regex("""a/(\d+)\?slug=(.+)""")
|
||||
val (animeId, animeTitle) = regex.find(url)!!.destructured
|
||||
val link = getAnimeByIdAndTitle(animeTitle, animeId.toInt())!!
|
||||
|
||||
val html = app.get(link).text
|
||||
val doc = Jsoup.parse(html)
|
||||
|
||||
val japTitle = doc.selectFirst("h2.japanese")?.text()
|
||||
val poster = doc.selectFirst(".anime-poster a")?.attr("href")
|
||||
|
||||
val tvType = doc.selectFirst("""a[href*="/anime/type/"]""")?.text()
|
||||
|
||||
val trailer: String? = if (html.contains("https://www.youtube.com/watch")) {
|
||||
YOUTUBE_VIDEO_LINK.find(html)?.destructured?.component1()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val episodes = generateListOfEpisodes(url)
|
||||
val year = Regex("""<strong>Aired:</strong>[^,]*, (\d+)""")
|
||||
.find(html)!!.destructured.component1()
|
||||
.toIntOrNull()
|
||||
val status =
|
||||
when (Regex("""<strong>Status:</strong>[^a]*a href=["']/anime/(.*?)["']""")
|
||||
.find(html)!!.destructured.component1()) {
|
||||
"airing" -> ShowStatus.Ongoing
|
||||
"completed" -> ShowStatus.Completed
|
||||
else -> null
|
||||
}
|
||||
val synopsis = doc.selectFirst(".anime-synopsis")?.text()
|
||||
|
||||
var anilistId: Int? = null
|
||||
var malId: Int? = null
|
||||
|
||||
doc.select(".external-links > a").forEach { aTag ->
|
||||
val split = aTag.attr("href").split("/")
|
||||
|
||||
if (aTag.attr("href").contains("anilist.co")) {
|
||||
anilistId = split[split.size - 1].toIntOrNull()
|
||||
} else if (aTag.attr("href").contains("myanimelist.net")) {
|
||||
malId = split[split.size - 1].toIntOrNull()
|
||||
}
|
||||
}
|
||||
|
||||
newAnimeLoadResponse(animeTitle, url, getType(tvType.toString())) {
|
||||
engName = animeTitle
|
||||
japName = japTitle
|
||||
|
||||
this.posterUrl = poster
|
||||
this.year = year
|
||||
|
||||
addEpisodes(DubStatus.Subbed, episodes)
|
||||
this.showStatus = status
|
||||
plot = synopsis
|
||||
tags = if (!doc.select(".anime-genre > ul a").isEmpty()) {
|
||||
ArrayList(doc.select(".anime-genre > ul a").map { it.text().toString() })
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
addMalId(malId)
|
||||
addAniListId(anilistId)
|
||||
addTrailer(trailer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun isNumber(s: String?): Boolean {
|
||||
return s?.toIntOrNull() != null
|
||||
}
|
||||
|
||||
private fun cookieStrToMap(cookie: String): Map<String, String> {
|
||||
val cookies = mutableMapOf<String, String>()
|
||||
for (string in cookie.split("; ")) {
|
||||
val split = string.split("=").toMutableList()
|
||||
val name = split.removeFirst().trim()
|
||||
val value = if (split.size == 0) {
|
||||
"true"
|
||||
} else {
|
||||
split.joinToString("=")
|
||||
}
|
||||
cookies[name] = value
|
||||
}
|
||||
return cookies.toMap()
|
||||
}
|
||||
|
||||
private fun getString(content: String, s1: Int, s2: Int): String {
|
||||
val characterMap = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/"
|
||||
|
||||
val slice2 = characterMap.slice(0 until s2)
|
||||
var acc: Long = 0
|
||||
|
||||
for ((n, i) in content.reversed().withIndex()) {
|
||||
acc += (when (isNumber("$i")) {
|
||||
true -> "$i".toLong()
|
||||
false -> "0".toLong()
|
||||
}) * s1.toDouble().pow(n.toDouble()).toInt()
|
||||
}
|
||||
|
||||
var k = ""
|
||||
|
||||
while (acc > 0) {
|
||||
k = slice2[(acc % s2).toInt()] + k
|
||||
acc = (acc - (acc % s2)) / s2
|
||||
}
|
||||
|
||||
return when (k != "") {
|
||||
true -> k
|
||||
false -> "0"
|
||||
}
|
||||
}
|
||||
|
||||
private fun decrypt(fullString: String, key: String, v1: Int, v2: Int): String {
|
||||
var r = ""
|
||||
var i = 0
|
||||
|
||||
while (i < fullString.length) {
|
||||
var s = ""
|
||||
|
||||
while (fullString[i] != key[v2]) {
|
||||
s += fullString[i]
|
||||
++i
|
||||
}
|
||||
var j = 0
|
||||
|
||||
while (j < key.length) {
|
||||
s = s.replace(key[j].toString(), j.toString())
|
||||
++j
|
||||
}
|
||||
r += (getString(s, v2, 10).toInt() - v1).toChar()
|
||||
++i
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
private fun zipGen(gen: Sequence<Pair<Int, Int>>): ArrayList<Pair<Pair<Int, Int>, Pair<Int, Int>>> {
|
||||
val allItems = gen.toList().toMutableList()
|
||||
val newList = ArrayList<Pair<Pair<Int, Int>, Pair<Int, Int>>>()
|
||||
|
||||
while (allItems.size > 1) {
|
||||
newList.add(Pair(allItems[0], allItems[1]))
|
||||
allItems.removeAt(0)
|
||||
allItems.removeAt(0)
|
||||
}
|
||||
return newList
|
||||
}
|
||||
|
||||
private fun decodeAdfly(codedKey: String): String {
|
||||
var r = ""
|
||||
var j = ""
|
||||
|
||||
for ((n, l) in codedKey.withIndex()) {
|
||||
if (n % 2 != 0) {
|
||||
j = l + j
|
||||
} else {
|
||||
r += l
|
||||
}
|
||||
}
|
||||
|
||||
val encodedUri = ((r + j).toCharArray().map { it.toString() }).toMutableList()
|
||||
val numbers = sequence {
|
||||
for ((i, n) in encodedUri.withIndex()) {
|
||||
if (isNumber(n)) {
|
||||
yield(Pair(i, n.toInt()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for ((first, second) in zipGen(numbers)) {
|
||||
val xor = first.second.xor(second.second)
|
||||
if (xor < 10) {
|
||||
encodedUri[first.first] = xor.toString()
|
||||
}
|
||||
}
|
||||
var returnValue = String(encodedUri.joinToString("").toByteArray(), Charsets.UTF_8)
|
||||
returnValue = base64Decode(returnValue)
|
||||
return returnValue.slice(16..returnValue.length - 17)
|
||||
}
|
||||
|
||||
private data class VideoQuality(
|
||||
@JsonProperty("id") val id: Int?,
|
||||
@JsonProperty("audio") val audio: String?,
|
||||
@JsonProperty("kwik") val kwik: String?,
|
||||
@JsonProperty("kwik_pahewin") val kwikPahewin: String
|
||||
)
|
||||
|
||||
private data class AnimePaheEpisodeLoadLinks(
|
||||
@JsonProperty("data") val data: List<Map<String, VideoQuality>>
|
||||
)
|
||||
|
||||
private suspend fun bypassAdfly(adflyUri: String): String {
|
||||
if (!generateSession()) {
|
||||
return bypassAdfly(adflyUri)
|
||||
}
|
||||
|
||||
var responseCode = 302
|
||||
var adflyContent: NiceResponse? = null
|
||||
var tries = 0
|
||||
|
||||
while (responseCode != 200 && tries < 20) {
|
||||
adflyContent = app.get(
|
||||
app.get(adflyUri, cookies = cookies, allowRedirects = false).url,
|
||||
cookies = cookies,
|
||||
allowRedirects = false
|
||||
)
|
||||
cookies = cookies + adflyContent.cookies
|
||||
responseCode = adflyContent.code
|
||||
++tries
|
||||
}
|
||||
if (tries > 19) {
|
||||
throw Exception("Failed to bypass adfly.")
|
||||
}
|
||||
return decodeAdfly(YTSM.find(adflyContent?.text.toString())!!.destructured.component1())
|
||||
}
|
||||
|
||||
private suspend fun getStreamUrlFromKwik(url: String?): String? {
|
||||
if (url == null) return null
|
||||
val response =
|
||||
app.get(
|
||||
url,
|
||||
headers = mapOf("referer" to mainUrl),
|
||||
cookies = cookies
|
||||
).text
|
||||
Regex("eval((.|\\n)*?)</script>").find(response)?.groupValues?.get(1)?.let { jsEval ->
|
||||
JsUnpacker("eval$jsEval").unpack()?.let { unPacked ->
|
||||
Regex("source=\'(.*?)\'").find(unPacked)?.groupValues?.get(1)?.let { link ->
|
||||
return link
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private suspend fun getStreamUrlFromKwikAdfly(adflyUri: String): String {
|
||||
val fContent =
|
||||
app.get(
|
||||
bypassAdfly(adflyUri),
|
||||
headers = mapOf("referer" to "https://kwik.cx/"),
|
||||
cookies = cookies
|
||||
)
|
||||
cookies = cookies + fContent.cookies
|
||||
|
||||
val (fullString, key, v1, v2) = KWIK_PARAMS_RE.find(fContent.text)!!.destructured
|
||||
val decrypted = decrypt(fullString, key, v1.toInt(), v2.toInt())
|
||||
val uri = KWIK_D_URL.find(decrypted)!!.destructured.component1()
|
||||
val tok = KWIK_D_TOKEN.find(decrypted)!!.destructured.component1()
|
||||
var content: NiceResponse? = null
|
||||
|
||||
var code = 419
|
||||
var tries = 0
|
||||
|
||||
while (code != 302 && tries < 20) {
|
||||
content = app.post(
|
||||
uri,
|
||||
allowRedirects = false,
|
||||
data = mapOf("_token" to tok),
|
||||
headers = mapOf("referer" to fContent.url),
|
||||
cookies = fContent.cookies
|
||||
)
|
||||
code = content.code
|
||||
++tries
|
||||
}
|
||||
if (tries > 19) {
|
||||
throw Exception("Failed to extract the stream uri from kwik.")
|
||||
}
|
||||
return content?.headers?.values("location").toString()
|
||||
}
|
||||
|
||||
private suspend fun extractVideoLinks(
|
||||
episodeLink: String,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
var link = episodeLink
|
||||
val headers = mapOf("referer" to "$mainUrl/")
|
||||
|
||||
if (link.contains("!!TRUE!!")) {
|
||||
link = link.replace("!!TRUE!!", "")
|
||||
} else {
|
||||
val regex = """&ep=(\d+)!!FALSE!!""".toRegex()
|
||||
val episodeNum = regex.find(link)?.destructured?.component1()?.toIntOrNull()
|
||||
link = link.replace(regex, "")
|
||||
|
||||
val req = app.get(link, headers = headers).text
|
||||
val jsonResponse = parseJson<AnimePaheAnimeData>(req)
|
||||
val ep = ((jsonResponse.data.map {
|
||||
if (it.episode == episodeNum) {
|
||||
it
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}).filterNotNull())[0]
|
||||
link = "$mainUrl/api?m=links&id=${ep.animeId}&session=${ep.session}&p=kwik"
|
||||
}
|
||||
val req = app.get(link, headers = headers).text
|
||||
val data = mapper.readValue<AnimePaheEpisodeLoadLinks>(req)
|
||||
|
||||
data.data.forEach {
|
||||
it.entries.toList().apmap { quality ->
|
||||
getStreamUrlFromKwik(quality.value.kwik)?.let { link ->
|
||||
callback(
|
||||
ExtractorLink(
|
||||
"KWIK",
|
||||
"KWIK - ${quality.key} [${quality.value.audio ?: "jpn"}]",
|
||||
link,
|
||||
"https://kwik.cx/",
|
||||
getQualityFromName(quality.key),
|
||||
link.contains(".m3u8")
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
extractVideoLinks(data, callback)
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -1,191 +0,0 @@
|
|||
package com.lagradost.cloudstream3.animeproviders
|
||||
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
import com.lagradost.nicehttp.NiceResponse
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Element
|
||||
|
||||
class AnimeSailProvider : MainAPI() {
|
||||
override var mainUrl = "https://111.90.143.42"
|
||||
override var name = "AnimeSail"
|
||||
override val hasMainPage = true
|
||||
override var lang = "id"
|
||||
override val hasDownloadSupport = true
|
||||
|
||||
override val supportedTypes = setOf(
|
||||
TvType.Anime,
|
||||
TvType.AnimeMovie,
|
||||
TvType.OVA
|
||||
)
|
||||
|
||||
companion object {
|
||||
fun getType(t: String): TvType {
|
||||
return if (t.contains("OVA") || t.contains("Special")) TvType.OVA
|
||||
else if (t.contains("Movie")) TvType.AnimeMovie
|
||||
else TvType.Anime
|
||||
}
|
||||
|
||||
fun getStatus(t: String): ShowStatus {
|
||||
return when (t) {
|
||||
"Completed" -> ShowStatus.Completed
|
||||
"Ongoing" -> ShowStatus.Ongoing
|
||||
else -> ShowStatus.Completed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun request(url: String, ref: String? = null): NiceResponse {
|
||||
return app.get(
|
||||
url,
|
||||
headers = mapOf("Accept" to "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"),
|
||||
cookies = mapOf("_as_ipin_ct" to "ID"),
|
||||
referer = ref
|
||||
)
|
||||
}
|
||||
|
||||
override val mainPage = mainPageOf(
|
||||
"$mainUrl/page/" to "Episode Terbaru",
|
||||
"$mainUrl/movie-terbaru/page/" to "Movie Terbaru",
|
||||
"$mainUrl/genres/donghua/page/" to "Donghua"
|
||||
)
|
||||
|
||||
override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse {
|
||||
val document = request(request.data + page).document
|
||||
val home = document.select("article").map {
|
||||
it.toSearchResult()
|
||||
}
|
||||
return newHomePageResponse(request.name, home)
|
||||
}
|
||||
|
||||
private fun getProperAnimeLink(uri: String): String {
|
||||
return if (uri.contains("/anime/")) {
|
||||
uri
|
||||
} else {
|
||||
var title = uri.substringAfter("$mainUrl/")
|
||||
title = when {
|
||||
(title.contains("-episode")) && !(title.contains("-movie")) -> title.substringBefore(
|
||||
"-episode"
|
||||
)
|
||||
(title.contains("-movie")) -> title.substringBefore("-movie")
|
||||
else -> title
|
||||
}
|
||||
|
||||
"$mainUrl/anime/$title"
|
||||
}
|
||||
}
|
||||
|
||||
private fun Element.toSearchResult(): AnimeSearchResponse {
|
||||
val href = getProperAnimeLink(fixUrlNull(this.selectFirst("a")?.attr("href")).toString())
|
||||
val title = this.select(".tt > h2").text().trim()
|
||||
val posterUrl = fixUrlNull(this.selectFirst("div.limit img")?.attr("src"))
|
||||
val epNum = this.selectFirst(".tt > h2")?.text()?.let {
|
||||
Regex("Episode\\s?([0-9]+)").find(it)?.groupValues?.getOrNull(1)?.toIntOrNull()
|
||||
}
|
||||
return newAnimeSearchResponse(title, href, TvType.Anime) {
|
||||
this.posterUrl = posterUrl
|
||||
addSub(epNum)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
val link = "$mainUrl/?s=$query"
|
||||
val document = request(link).document
|
||||
|
||||
return document.select("div.listupd article").map {
|
||||
it.toSearchResult()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val document = request(url).document
|
||||
|
||||
val title = document.selectFirst("h1.entry-title")?.text().toString().trim()
|
||||
val type = getType(
|
||||
document.select("tbody th:contains(Tipe)").next().text()
|
||||
)
|
||||
val episodes = document.select("ul.daftar > li").map {
|
||||
val header = it.select("a").text().trim()
|
||||
val name =
|
||||
Regex("(Episode\\s?[0-9]+)").find(header)?.groupValues?.getOrNull(0) ?: header
|
||||
val link = fixUrl(it.select("a").attr("href"))
|
||||
Episode(link, name = name)
|
||||
}.reversed()
|
||||
|
||||
return newAnimeLoadResponse(title, url, type) {
|
||||
posterUrl = document.selectFirst("div.entry-content > img")?.attr("src")
|
||||
this.year =
|
||||
document.select("tbody th:contains(Dirilis)").next().text().trim().toIntOrNull()
|
||||
addEpisodes(DubStatus.Subbed, episodes)
|
||||
showStatus =
|
||||
getStatus(document.select("tbody th:contains(Status)").next().text().trim())
|
||||
plot = document.selectFirst("div.entry-content > p")?.text()
|
||||
this.tags =
|
||||
document.select("tbody th:contains(Genre)").next().select("a").map { it.text() }
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
|
||||
val document = request(data).document
|
||||
|
||||
document.select(".mobius > .mirror > option").apmap {
|
||||
safeApiCall {
|
||||
val iframe = fixUrl(
|
||||
Jsoup.parse(base64Decode(it.attr("data-em"))).select("iframe").attr("src")
|
||||
?: throw ErrorLoadingException("No iframe found")
|
||||
)
|
||||
|
||||
when {
|
||||
iframe.startsWith("$mainUrl/utils/player/arch/") || iframe.startsWith(
|
||||
"$mainUrl/utils/player/race/"
|
||||
) -> request(iframe, ref = data).document.select("source").attr("src")
|
||||
.let { link ->
|
||||
val source =
|
||||
when {
|
||||
iframe.contains("/arch/") -> "Arch"
|
||||
iframe.contains("/race/") -> "Race"
|
||||
else -> this.name
|
||||
}
|
||||
val quality =
|
||||
Regex("\\.([0-9]{3,4})\\.").find(link)?.groupValues?.get(1)
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
source = source,
|
||||
name = source,
|
||||
url = link,
|
||||
referer = mainUrl,
|
||||
quality = quality?.toIntOrNull() ?: Qualities.Unknown.value
|
||||
)
|
||||
)
|
||||
}
|
||||
// skip for now
|
||||
// iframe.startsWith("$mainUrl/utils/player/fichan/") -> ""
|
||||
// iframe.startsWith("$mainUrl/utils/player/blogger/") -> ""
|
||||
iframe.startsWith("$mainUrl/utils/player/framezilla/") || iframe.startsWith("https://uservideo.xyz") -> {
|
||||
request(iframe, ref = data).document.select("iframe").attr("src")
|
||||
.let { link ->
|
||||
loadExtractor(fixUrl(link), mainUrl, subtitleCallback, callback)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
loadExtractor(iframe, mainUrl, subtitleCallback, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,201 +0,0 @@
|
|||
package com.lagradost.cloudstream3.animeproviders
|
||||
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addAniListId
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addDuration
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addMalId
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addRating
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
import org.jsoup.nodes.Element
|
||||
|
||||
class AnimeSaturnProvider : MainAPI() {
|
||||
override var mainUrl = "https://www.animesaturn.cc"
|
||||
override var name = "AnimeSaturn"
|
||||
override var lang = "it"
|
||||
override val hasMainPage = true
|
||||
|
||||
override val supportedTypes = setOf(
|
||||
TvType.Anime,
|
||||
TvType.AnimeMovie,
|
||||
TvType.OVA
|
||||
)
|
||||
|
||||
companion object {
|
||||
fun getStatus(t: String?): ShowStatus? {
|
||||
return when (t?.lowercase()) {
|
||||
"finito" -> ShowStatus.Completed
|
||||
"in corso" -> ShowStatus.Ongoing
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Element.toSearchResult(): AnimeSearchResponse {
|
||||
|
||||
var title = this.select("a.badge-archivio").first()!!.text()
|
||||
var isDubbed = false
|
||||
|
||||
if (title.contains(" (ITA)")){
|
||||
title = title.replace(" (ITA)", "")
|
||||
isDubbed = true
|
||||
}
|
||||
|
||||
val url = this.select("a.badge-archivio").first()!!.attr("href")
|
||||
|
||||
val posterUrl = this.select("img.locandina-archivio[src]").first()!!.attr("src")
|
||||
|
||||
return newAnimeSearchResponse(title, url, TvType.Anime) {
|
||||
addDubStatus(isDubbed)
|
||||
this.posterUrl = posterUrl
|
||||
}
|
||||
}
|
||||
|
||||
private fun Element.toEpisode(): Episode? {
|
||||
var episode = this.text().split(" ")[1]
|
||||
if(episode.contains(".")) return null
|
||||
if(episode.contains("-"))
|
||||
episode = episode.split("-")[0]
|
||||
|
||||
return Episode(
|
||||
data = this.attr("href"),
|
||||
episode = episode.toInt()
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse {
|
||||
val document = app.get(mainUrl).document
|
||||
val list = ArrayList<HomePageList>()
|
||||
document.select("div.container:has(span.badge-saturn)").forEach {
|
||||
val tabName = it.select("span.badge-saturn").first()!!.text()
|
||||
if (tabName.equals("Ultimi episodi")) return@forEach
|
||||
val results = ArrayList<AnimeSearchResponse>()
|
||||
it.select(".main-anime-card").forEach { card ->
|
||||
var title = card.select("a[title]").first()!!.attr("title")
|
||||
var isDubbed = false
|
||||
if(title.contains(" (ITA)")){
|
||||
title = title.replace(" (ITA)", "")
|
||||
isDubbed = true
|
||||
}
|
||||
val posterUrl = card.select("img.new-anime").first()!!.attr("src")
|
||||
val url = card.select("a").first()!!.attr("href")
|
||||
|
||||
results.add(newAnimeSearchResponse(title, url, TvType.Anime){
|
||||
addDubStatus(isDubbed)
|
||||
this.posterUrl = posterUrl
|
||||
})
|
||||
}
|
||||
list.add(HomePageList(tabName, results))
|
||||
}
|
||||
return HomePageResponse(list)
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
val document = app.get("$mainUrl/animelist?search=$query").document
|
||||
return document.select("div.item-archivio").map {
|
||||
it.toSearchResult()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
|
||||
val document = app.get(url).document
|
||||
|
||||
val title = document.select("img.cover-anime").first()!!.attr("alt")
|
||||
val japTitle = document.select("div.box-trasparente-alternativo").first()!!.text()
|
||||
val posterUrl = document.select("img.cover-anime[src]").first()!!.attr("src")
|
||||
var malId : Int? = null
|
||||
var aniListId : Int? = null
|
||||
|
||||
document.select("[rel=\"noopener noreferrer\"]").forEach {
|
||||
if(it.attr("href").contains("myanimelist"))
|
||||
malId = it.attr("href").removeSuffix("/").split('/').last().toIntOrNull()
|
||||
else
|
||||
aniListId = it.attr("href").removeSuffix("/").split('/').last().toIntOrNull()
|
||||
}
|
||||
|
||||
val plot = document.select("div#shown-trama").first()?.text()
|
||||
|
||||
val tags = document.select("a.generi-as").map { it.text() }
|
||||
|
||||
val details : List<String>? = document.select("div.container:contains(Stato: )").first()?.text()?.split(" ")
|
||||
var status : String? = null
|
||||
var duration : String? = null
|
||||
var year : String? = null
|
||||
var score : String? = null
|
||||
|
||||
val isDubbed = document.select("div.anime-title-as").first()!!.text().contains("(ITA)")
|
||||
|
||||
if (!details.isNullOrEmpty()) {
|
||||
details.forEach {
|
||||
val index = details.indexOf(it) +1
|
||||
when (it) {
|
||||
"Stato:" -> status = details[index]
|
||||
"episodi:" -> duration = details[index]
|
||||
"uscita:" -> year = details[index + 2]
|
||||
"Voto:" -> score = details[index].split("/")[0]
|
||||
else -> return@forEach
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val episodes = document.select("a.bottone-ep").mapNotNull{ it.toEpisode() }
|
||||
|
||||
return newAnimeLoadResponse(title, url, TvType.Anime) {
|
||||
this.engName = title
|
||||
this.japName = japTitle
|
||||
this.year = year?.toIntOrNull()
|
||||
this.plot = plot
|
||||
this.tags = tags
|
||||
this.showStatus = getStatus(status)
|
||||
addPoster(posterUrl)
|
||||
addRating(score)
|
||||
addEpisodes(if (isDubbed) DubStatus.Dubbed else DubStatus.Subbed, episodes)
|
||||
addMalId(malId)
|
||||
addAniListId(aniListId)
|
||||
addDuration(duration)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
|
||||
val page = app.get(data).document
|
||||
val episodeLink = page.select("div.card-body > a[href]").find {it1 ->
|
||||
it1.attr("href").contains("watch?")
|
||||
}?.attr("href")
|
||||
|
||||
val episodePage = app.get(episodeLink!!).document
|
||||
val episodeUrl: String?
|
||||
var isM3U8 = false
|
||||
|
||||
if(episodePage.select("video.afterglow > source").isNotEmpty()) //Old player
|
||||
episodeUrl = episodePage.select("video.afterglow > source").first()!!.attr("src")
|
||||
|
||||
else{ //New player
|
||||
val script = episodePage.select("script").find {
|
||||
it.toString().contains("jwplayer('player_hls').setup({")
|
||||
}!!.toString()
|
||||
episodeUrl = script.split(" ").find { it.contains(".m3u8") and !it.contains(".replace") }!!.replace("\"","").replace(",", "")
|
||||
isM3U8 = true
|
||||
}
|
||||
|
||||
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
name,
|
||||
name,
|
||||
episodeUrl!!,
|
||||
isM3u8 = isM3U8,
|
||||
referer = "https://www.animesaturn.io/", //Some servers need the old host as referer, and the new ones accept it too
|
||||
quality = Qualities.Unknown.value
|
||||
)
|
||||
)
|
||||
return true
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -1,249 +0,0 @@
|
|||
package com.lagradost.cloudstream3.animeproviders
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
import org.jsoup.Jsoup
|
||||
import java.util.*
|
||||
|
||||
|
||||
class AnimefenixProvider:MainAPI() {
|
||||
|
||||
override var mainUrl = "https://animefenix.com"
|
||||
override var name = "Animefenix"
|
||||
override var lang = "es"
|
||||
override val hasMainPage = true
|
||||
override val hasChromecastSupport = true
|
||||
override val hasDownloadSupport = true
|
||||
override val supportedTypes = setOf(
|
||||
TvType.AnimeMovie,
|
||||
TvType.OVA,
|
||||
TvType.Anime,
|
||||
)
|
||||
|
||||
fun getDubStatus(title: String): DubStatus {
|
||||
return if (title.contains("Latino") || title.contains("Castellano"))
|
||||
DubStatus.Dubbed
|
||||
else DubStatus.Subbed
|
||||
}
|
||||
|
||||
override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse {
|
||||
val urls = listOf(
|
||||
Pair("$mainUrl/", "Animes"),
|
||||
Pair("$mainUrl/animes?type[]=movie&order=default", "Peliculas", ),
|
||||
Pair("$mainUrl/animes?type[]=ova&order=default", "OVA's", ),
|
||||
)
|
||||
|
||||
val items = ArrayList<HomePageList>()
|
||||
|
||||
items.add(
|
||||
HomePageList(
|
||||
"Últimos episodios",
|
||||
app.get(mainUrl).document.select(".capitulos-grid div.item").map {
|
||||
val title = it.selectFirst("div.overtitle")?.text()
|
||||
val poster = it.selectFirst("a img")?.attr("src")
|
||||
val epRegex = Regex("(-(\\d+)\$|-(\\d+)\\.(\\d+))")
|
||||
val url = it.selectFirst("a")?.attr("href")?.replace(epRegex,"")
|
||||
?.replace("/ver/","/")
|
||||
val epNum = it.selectFirst(".is-size-7")?.text()?.replace("Episodio ","")?.toIntOrNull()
|
||||
newAnimeSearchResponse(title!!, url!!) {
|
||||
this.posterUrl = poster
|
||||
addDubStatus(getDubStatus(title), epNum)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
urls.apmap { (url, name) ->
|
||||
val response = app.get(url)
|
||||
val soup = Jsoup.parse(response.text)
|
||||
val home = soup.select(".list-series article").map {
|
||||
val title = it.selectFirst("h3 a")?.text()
|
||||
val poster = it.selectFirst("figure img")?.attr("src")
|
||||
AnimeSearchResponse(
|
||||
title!!,
|
||||
it.selectFirst("a")?.attr("href") ?: "",
|
||||
this.name,
|
||||
TvType.Anime,
|
||||
poster,
|
||||
null,
|
||||
if (title.contains("Latino")) EnumSet.of(DubStatus.Dubbed) else EnumSet.of(DubStatus.Subbed),
|
||||
)
|
||||
}
|
||||
|
||||
items.add(HomePageList(name, home))
|
||||
}
|
||||
|
||||
if (items.size <= 0) throw ErrorLoadingException()
|
||||
return HomePageResponse(items)
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
return app.get("$mainUrl/animes?q=$query").document.select(".list-series article").map {
|
||||
val title = it.selectFirst("h3 a")?.text()
|
||||
val href = it.selectFirst("a")?.attr("href")
|
||||
val image = it.selectFirst("figure img")?.attr("src")
|
||||
AnimeSearchResponse(
|
||||
title!!,
|
||||
href!!,
|
||||
this.name,
|
||||
TvType.Anime,
|
||||
fixUrl(image ?: ""),
|
||||
null,
|
||||
if (title.contains("Latino") || title.contains("Castellano")) EnumSet.of(DubStatus.Dubbed) else EnumSet.of(
|
||||
DubStatus.Subbed),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val doc = Jsoup.parse(app.get(url, timeout = 120).text)
|
||||
val poster = doc.selectFirst(".image > img")?.attr("src")
|
||||
val title = doc.selectFirst("h1.title.has-text-orange")?.text()
|
||||
val description = doc.selectFirst("p.has-text-light")?.text()
|
||||
val genres = doc.select(".genres a").map { it.text() }
|
||||
val status = when (doc.selectFirst(".is-narrow-desktop a.button")?.text()) {
|
||||
"Emisión" -> ShowStatus.Ongoing
|
||||
"Finalizado" -> ShowStatus.Completed
|
||||
else -> null
|
||||
}
|
||||
val episodes = doc.select(".anime-page__episode-list li").map {
|
||||
val name = it.selectFirst("span")?.text()
|
||||
val link = it.selectFirst("a")?.attr("href")
|
||||
Episode(link!!, name)
|
||||
}.reversed()
|
||||
val type = if (doc.selectFirst("ul.has-text-light")?.text()
|
||||
!!.contains("Película") && episodes.size == 1
|
||||
) TvType.AnimeMovie else TvType.Anime
|
||||
return newAnimeLoadResponse(title!!, url, type) {
|
||||
japName = null
|
||||
engName = title
|
||||
posterUrl = poster
|
||||
addEpisodes(DubStatus.Subbed, episodes)
|
||||
plot = description
|
||||
tags = genres
|
||||
showStatus = status
|
||||
}
|
||||
}
|
||||
|
||||
private fun cleanStreamID(input: String): String = input.replace(Regex("player=.*&code=|&"),"")
|
||||
|
||||
data class Amazon (
|
||||
@JsonProperty("file") var file : String? = null,
|
||||
@JsonProperty("type") var type : String? = null,
|
||||
@JsonProperty("label") var label : String? = null
|
||||
)
|
||||
|
||||
private fun cleanExtractor(
|
||||
source: String,
|
||||
name: String,
|
||||
url: String,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
callback(
|
||||
ExtractorLink(
|
||||
source,
|
||||
name,
|
||||
url,
|
||||
"",
|
||||
Qualities.Unknown.value,
|
||||
false
|
||||
)
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
val soup = app.get(data).document
|
||||
val script = soup.selectFirst(".player-container script")?.data()
|
||||
if (script!!.contains("var tabsArray =")) {
|
||||
val sourcesRegex = Regex("player=.*&code(.*)&")
|
||||
val test = sourcesRegex.findAll(script).toList()
|
||||
test.apmap {
|
||||
val codestream = it.value
|
||||
val links = when {
|
||||
codestream.contains("player=2&") -> "https://embedsito.com/v/"+cleanStreamID(codestream)
|
||||
codestream.contains("player=3&") -> "https://www.mp4upload.com/embed-"+cleanStreamID(codestream)+".html"
|
||||
codestream.contains("player=6&") -> "https://www.yourupload.com/embed/"+cleanStreamID(codestream)
|
||||
codestream.contains("player=12&") -> "http://ok.ru/videoembed/"+cleanStreamID(codestream)
|
||||
codestream.contains("player=4&") -> "https://sendvid.com/"+cleanStreamID(codestream)
|
||||
codestream.contains("player=9&") -> "AmaNormal https://www.animefenix.com/stream/amz.php?v="+cleanStreamID(codestream)
|
||||
codestream.contains("player=11&") -> "AmazonES https://www.animefenix.com/stream/amz.php?v="+cleanStreamID(codestream)
|
||||
codestream.contains("player=22&") -> "Fireload https://www.animefenix.com/stream/fl.php?v="+cleanStreamID(codestream)
|
||||
|
||||
else -> ""
|
||||
}
|
||||
loadExtractor(links, data, subtitleCallback, callback)
|
||||
|
||||
argamap({
|
||||
if (links.contains("AmaNormal")) {
|
||||
val doc = app.get(links.replace("AmaNormal ","")).document
|
||||
doc.select("script").map { script ->
|
||||
if (script.data().contains("sources: [{\"file\"")) {
|
||||
val text = script.data().substringAfter("sources:").substringBefore("]").replace("[","")
|
||||
val json = parseJson<Amazon>(text)
|
||||
if (json.file != null) {
|
||||
cleanExtractor(
|
||||
"Amazon",
|
||||
"Amazon ${json.label}",
|
||||
json.file!!,
|
||||
callback
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (links.contains("AmazonES")) {
|
||||
val amazonES = links.replace("AmazonES ", "")
|
||||
val doc = app.get("$amazonES&ext=es").document
|
||||
doc.select("script").map { script ->
|
||||
if (script.data().contains("sources: [{\"file\"")) {
|
||||
val text = script.data().substringAfter("sources:").substringBefore("]").replace("[","")
|
||||
val json = parseJson<Amazon>(text)
|
||||
if (json.file != null) {
|
||||
cleanExtractor(
|
||||
"AmazonES",
|
||||
"AmazonES ${json.label}",
|
||||
json.file!!,
|
||||
callback
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (links.contains("Fireload")) {
|
||||
val doc = app.get(links.replace("Fireload ", "")).document
|
||||
doc.select("script").map { script ->
|
||||
if (script.data().contains("sources: [{\"file\"")) {
|
||||
val text = script.data().substringAfter("sources:").substringBefore("]").replace("[","")
|
||||
val json = parseJson<Amazon>(text)
|
||||
val testurl = if (json.file?.contains("fireload") == true) {
|
||||
app.get("https://${json.file}").text
|
||||
} else null
|
||||
if (testurl?.contains("error") == true) {
|
||||
//
|
||||
} else if (json.file?.contains("fireload") == true) {
|
||||
cleanExtractor(
|
||||
"Fireload",
|
||||
"Fireload ${json.label}",
|
||||
"https://"+json.file!!,
|
||||
callback
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -1,229 +0,0 @@
|
|||
package com.lagradost.cloudstream3.movieproviders
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
import java.util.*
|
||||
|
||||
class AnimeflvIOProvider:MainAPI() {
|
||||
override var mainUrl = "https://animeflv.io" //Also scrapes from animeid.to
|
||||
override var name = "Animeflv.io"
|
||||
override var lang = "es"
|
||||
override val hasMainPage = true
|
||||
override val hasChromecastSupport = true
|
||||
override val hasDownloadSupport = true
|
||||
override val supportedTypes = setOf(
|
||||
TvType.AnimeMovie,
|
||||
TvType.OVA,
|
||||
TvType.Anime,
|
||||
)
|
||||
override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse {
|
||||
val items = ArrayList<HomePageList>()
|
||||
val urls = listOf(
|
||||
Pair("$mainUrl/series", "Series actualizadas",),
|
||||
Pair("$mainUrl/peliculas", "Peliculas actualizadas"),
|
||||
)
|
||||
items.add(HomePageList("Estrenos", app.get(mainUrl).document.select("div#owl-demo-premiere-movies .pull-left").map{
|
||||
val title = it.selectFirst("p")?.text() ?: ""
|
||||
AnimeSearchResponse(
|
||||
title,
|
||||
fixUrl(it.selectFirst("a")?.attr("href") ?: ""),
|
||||
this.name,
|
||||
TvType.Anime,
|
||||
it.selectFirst("img")?.attr("src"),
|
||||
it.selectFirst("span.year").toString().toIntOrNull(),
|
||||
EnumSet.of(DubStatus.Subbed),
|
||||
)
|
||||
}))
|
||||
urls.apmap { (url, name) ->
|
||||
val soup = app.get(url).document
|
||||
val home = soup.select("div.item-pelicula").map {
|
||||
val title = it.selectFirst(".item-detail p")?.text() ?: ""
|
||||
val poster = it.selectFirst("figure img")?.attr("src")
|
||||
AnimeSearchResponse(
|
||||
title,
|
||||
fixUrl(it.selectFirst("a")?.attr("href") ?: ""),
|
||||
this.name,
|
||||
TvType.Anime,
|
||||
poster,
|
||||
null,
|
||||
if (title.contains("Latino") || title.contains("Castellano")) EnumSet.of(DubStatus.Dubbed) else EnumSet.of(DubStatus.Subbed),
|
||||
)
|
||||
}
|
||||
|
||||
items.add(HomePageList(name, home))
|
||||
}
|
||||
|
||||
if (items.size <= 0) throw ErrorLoadingException()
|
||||
return HomePageResponse(items)
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
val headers = mapOf(
|
||||
"Host" to "animeflv.io",
|
||||
"User-Agent" to USER_AGENT,
|
||||
"X-Requested-With" to "XMLHttpRequest",
|
||||
"DNT" to "1",
|
||||
"Alt-Used" to "animeflv.io",
|
||||
"Connection" to "keep-alive",
|
||||
"Referer" to "https://animeflv.io",
|
||||
)
|
||||
val url = "$mainUrl/search.html?keyword=$query"
|
||||
val document = app.get(
|
||||
url,
|
||||
headers = headers
|
||||
).document
|
||||
return document.select(".item-pelicula.pull-left").map {
|
||||
val title = it.selectFirst("div.item-detail p")?.text() ?: ""
|
||||
val href = fixUrl(it.selectFirst("a")?.attr("href") ?: "")
|
||||
var image = it.selectFirst("figure img")?.attr("src") ?: ""
|
||||
val isMovie = href.contains("/pelicula/")
|
||||
if (image.contains("/static/img/picture.png")) { image = ""}
|
||||
if (isMovie) {
|
||||
MovieSearchResponse(
|
||||
title,
|
||||
href,
|
||||
this.name,
|
||||
TvType.AnimeMovie,
|
||||
image,
|
||||
null
|
||||
)
|
||||
} else {
|
||||
AnimeSearchResponse(
|
||||
title,
|
||||
href,
|
||||
this.name,
|
||||
TvType.Anime,
|
||||
image,
|
||||
null,
|
||||
EnumSet.of(DubStatus.Subbed),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse? {
|
||||
// Gets the url returned from searching.
|
||||
val soup = app.get(url).document
|
||||
val title = soup.selectFirst(".info-content h1")?.text()
|
||||
val description = soup.selectFirst("span.sinopsis")?.text()?.trim()
|
||||
val poster: String? = soup.selectFirst(".poster img")?.attr("src")
|
||||
val episodes = soup.select(".item-season-episodes a").map { li ->
|
||||
val href = fixUrl(li.selectFirst("a")?.attr("href") ?: "")
|
||||
val name = li.selectFirst("a")?.text() ?: ""
|
||||
Episode(
|
||||
href, name,
|
||||
)
|
||||
}.reversed()
|
||||
|
||||
val year = Regex("(\\d*)").find(soup.select(".info-half").text())
|
||||
|
||||
val tvType = if (url.contains("/pelicula/")) TvType.AnimeMovie else TvType.Anime
|
||||
val genre = soup.select(".content-type-a a")
|
||||
.map { it?.text()?.trim().toString().replace(", ","") }
|
||||
val duration = Regex("""(\d*)""").find(
|
||||
soup.select("p.info-half:nth-child(4)").text())
|
||||
|
||||
return when (tvType) {
|
||||
TvType.Anime -> {
|
||||
return newAnimeLoadResponse(title ?: "", url, tvType) {
|
||||
japName = null
|
||||
engName = title
|
||||
posterUrl = poster
|
||||
this.year = null
|
||||
addEpisodes(DubStatus.Subbed, episodes)
|
||||
plot = description
|
||||
tags = genre
|
||||
|
||||
showStatus = null
|
||||
}
|
||||
}
|
||||
TvType.AnimeMovie -> {
|
||||
MovieLoadResponse(
|
||||
title ?: "",
|
||||
url,
|
||||
this.name,
|
||||
tvType,
|
||||
url,
|
||||
poster,
|
||||
year.toString().toIntOrNull(),
|
||||
description,
|
||||
null,
|
||||
genre,
|
||||
duration.toString().toIntOrNull(),
|
||||
)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
data class MainJson (
|
||||
@JsonProperty("source") val source: List<Source>,
|
||||
@JsonProperty("source_bk") val sourceBk: String?,
|
||||
@JsonProperty("track") val track: List<String>?,
|
||||
@JsonProperty("advertising") val advertising: List<String>?,
|
||||
@JsonProperty("linkiframe") val linkiframe: String?
|
||||
)
|
||||
|
||||
data class Source (
|
||||
@JsonProperty("file") val file: String,
|
||||
@JsonProperty("label") val label: String,
|
||||
@JsonProperty("default") val default: String,
|
||||
@JsonProperty("type") val type: String
|
||||
)
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
app.get(data).document.select("li.tab-video").apmap {
|
||||
val url = fixUrl(it.attr("data-video"))
|
||||
if (url.contains("animeid")) {
|
||||
val ajaxurl = url.replace("streaming.php","ajax.php")
|
||||
val ajaxurltext = app.get(ajaxurl).text
|
||||
val json = parseJson<MainJson>(ajaxurltext)
|
||||
json.source.forEach { source ->
|
||||
if (source.file.contains("m3u8")) {
|
||||
generateM3u8(
|
||||
"Animeflv.io",
|
||||
source.file,
|
||||
"https://animeid.to",
|
||||
headers = mapOf("Referer" to "https://animeid.to")
|
||||
).apmap {
|
||||
callback(
|
||||
ExtractorLink(
|
||||
"Animeflv.io",
|
||||
"Animeflv.io",
|
||||
it.url,
|
||||
"https://animeid.to",
|
||||
getQualityFromName(it.quality.toString()),
|
||||
it.url.contains("m3u8")
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
callback(
|
||||
ExtractorLink(
|
||||
name,
|
||||
"$name ${source.label}",
|
||||
source.file,
|
||||
"https://animeid.to",
|
||||
Qualities.Unknown.value,
|
||||
isM3u8 = source.file.contains("m3u8")
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
loadExtractor(url, data, subtitleCallback, callback)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -1,182 +0,0 @@
|
|||
package com.lagradost.cloudstream3.animeproviders
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
import java.util.*
|
||||
|
||||
class AnimeflvnetProvider : MainAPI() {
|
||||
companion object {
|
||||
fun getType(t: String): TvType {
|
||||
return if (t.contains("OVA") || t.contains("Especial")) TvType.OVA
|
||||
else if (t.contains("Película")) TvType.AnimeMovie
|
||||
else TvType.Anime
|
||||
}
|
||||
|
||||
fun getDubStatus(title: String): DubStatus {
|
||||
return if (title.contains("Latino") || title.contains("Castellano"))
|
||||
DubStatus.Dubbed
|
||||
else DubStatus.Subbed
|
||||
}
|
||||
}
|
||||
|
||||
override var mainUrl = "https://www3.animeflv.net"
|
||||
override var name = "Animeflv.net"
|
||||
override var lang = "es"
|
||||
override val hasMainPage = true
|
||||
override val hasChromecastSupport = true
|
||||
override val hasDownloadSupport = true
|
||||
override val supportedTypes = setOf(
|
||||
TvType.AnimeMovie,
|
||||
TvType.OVA,
|
||||
TvType.Anime,
|
||||
)
|
||||
|
||||
override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse {
|
||||
val urls = listOf(
|
||||
Pair("$mainUrl/browse?type[]=movie&order=updated", "Películas"),
|
||||
Pair("$mainUrl/browse?status[]=2&order=default", "Animes"),
|
||||
Pair("$mainUrl/browse?status[]=1&order=rating", "En emision"),
|
||||
)
|
||||
val items = ArrayList<HomePageList>()
|
||||
items.add(
|
||||
HomePageList(
|
||||
"Últimos episodios",
|
||||
app.get(mainUrl).document.select("main.Main ul.ListEpisodios li").mapNotNull {
|
||||
val title = it.selectFirst("strong.Title")?.text() ?: return@mapNotNull null
|
||||
val poster = it.selectFirst("span img")?.attr("src") ?: return@mapNotNull null
|
||||
val epRegex = Regex("(-(\\d+)\$)")
|
||||
val url = it.selectFirst("a")?.attr("href")?.replace(epRegex, "")
|
||||
?.replace("ver/", "anime/") ?: return@mapNotNull null
|
||||
val epNum =
|
||||
it.selectFirst("span.Capi")?.text()?.replace("Episodio ", "")?.toIntOrNull()
|
||||
newAnimeSearchResponse(title, url) {
|
||||
this.posterUrl = fixUrl(poster)
|
||||
addDubStatus(getDubStatus(title), epNum)
|
||||
}
|
||||
})
|
||||
)
|
||||
for ((url, name) in urls) {
|
||||
try {
|
||||
val doc = app.get(url).document
|
||||
val home = doc.select("ul.ListAnimes li article").mapNotNull {
|
||||
val title = it.selectFirst("h3.Title")?.text() ?: return@mapNotNull null
|
||||
val poster = it.selectFirst("figure img")?.attr("src") ?: return@mapNotNull null
|
||||
newAnimeSearchResponse(
|
||||
title,
|
||||
fixUrl(it.selectFirst("a")?.attr("href") ?: return@mapNotNull null)
|
||||
) {
|
||||
this.posterUrl = fixUrl(poster)
|
||||
addDubStatus(MonoschinosProvider.getDubStatus(title))
|
||||
}
|
||||
}
|
||||
|
||||
items.add(HomePageList(name, home))
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
if (items.size <= 0) throw ErrorLoadingException()
|
||||
return HomePageResponse(items)
|
||||
}
|
||||
|
||||
data class SearchObject(
|
||||
@JsonProperty("id") val id: String,
|
||||
@JsonProperty("title") val title: String,
|
||||
@JsonProperty("type") val type: String,
|
||||
@JsonProperty("last_id") val lastId: String,
|
||||
@JsonProperty("slug") val slug: String
|
||||
)
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
val response = app.post(
|
||||
"https://www3.animeflv.net/api/animes/search",
|
||||
data = mapOf(Pair("value", query))
|
||||
).text
|
||||
val json = parseJson<List<SearchObject>>(response)
|
||||
return json.map { searchr ->
|
||||
val title = searchr.title
|
||||
val href = "$mainUrl/anime/${searchr.slug}"
|
||||
val image = "$mainUrl/uploads/animes/covers/${searchr.id}.jpg"
|
||||
AnimeSearchResponse(
|
||||
title,
|
||||
href,
|
||||
this.name,
|
||||
TvType.Anime,
|
||||
fixUrl(image),
|
||||
null,
|
||||
if (title.contains("Latino") || title.contains("Castellano")) EnumSet.of(DubStatus.Dubbed) else EnumSet.of(
|
||||
DubStatus.Subbed
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val doc = app.get(url).document
|
||||
val episodes = ArrayList<Episode>()
|
||||
val title = doc.selectFirst("h1.Title")!!.text()
|
||||
val poster = doc.selectFirst("div.AnimeCover div.Image figure img")?.attr("src")!!
|
||||
val description = doc.selectFirst("div.Description p")?.text()
|
||||
val type = doc.selectFirst("span.Type")?.text() ?: ""
|
||||
val status = when (doc.selectFirst("p.AnmStts span")?.text()) {
|
||||
"En emision" -> ShowStatus.Ongoing
|
||||
"Finalizado" -> ShowStatus.Completed
|
||||
else -> null
|
||||
}
|
||||
val genre = doc.select("nav.Nvgnrs a")
|
||||
.map { it?.text()?.trim().toString() }
|
||||
|
||||
doc.select("script").map { script ->
|
||||
if (script.data().contains("var episodes = [")) {
|
||||
val data = script.data().substringAfter("var episodes = [").substringBefore("];")
|
||||
data.split("],").forEach {
|
||||
val epNum = it.removePrefix("[").substringBefore(",")
|
||||
// val epthumbid = it.removePrefix("[").substringAfter(",").substringBefore("]")
|
||||
val animeid = doc.selectFirst("div.Strs.RateIt")?.attr("data-id")
|
||||
val epthumb = "https://cdn.animeflv.net/screenshots/$animeid/$epNum/th_3.jpg"
|
||||
val link = url.replace("/anime/", "/ver/") + "-$epNum"
|
||||
episodes.add(
|
||||
Episode(
|
||||
link,
|
||||
null,
|
||||
posterUrl = epthumb,
|
||||
episode = epNum.toIntOrNull()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return newAnimeLoadResponse(title, url, getType(type)) {
|
||||
posterUrl = fixUrl(poster)
|
||||
addEpisodes(DubStatus.Subbed, episodes.reversed())
|
||||
showStatus = status
|
||||
plot = description
|
||||
tags = genre
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
app.get(data).document.select("script").apmap { script ->
|
||||
if (script.data().contains("var videos = {") || script.data()
|
||||
.contains("var anime_id =") || script.data().contains("server")
|
||||
) {
|
||||
val videos = script.data().replace("\\/", "/")
|
||||
fetchUrls(videos).map {
|
||||
it.replace("https://embedsb.com/e/", "https://watchsb.com/e/")
|
||||
.replace("https://ok.ru", "http://ok.ru")
|
||||
}.apmap {
|
||||
loadExtractor(it, data, subtitleCallback, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -1,131 +0,0 @@
|
|||
package com.lagradost.cloudstream3.animeproviders
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
import org.jsoup.Jsoup
|
||||
import java.util.*
|
||||
|
||||
|
||||
class AnimekisaProvider : MainAPI() {
|
||||
override var mainUrl = "https://animekisa.in"
|
||||
override var name = "Animekisa"
|
||||
override val hasMainPage = true
|
||||
override val hasChromecastSupport = true
|
||||
override val hasDownloadSupport = true
|
||||
override val supportedTypes = setOf(
|
||||
TvType.AnimeMovie,
|
||||
TvType.OVA,
|
||||
TvType.Anime,
|
||||
)
|
||||
|
||||
data class Response(
|
||||
@JsonProperty("html") val html: String
|
||||
)
|
||||
|
||||
override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse {
|
||||
val urls = listOf(
|
||||
Pair("$mainUrl/ajax/list/views?type=all", "All animes"),
|
||||
Pair("$mainUrl/ajax/list/views?type=day", "Trending now"),
|
||||
Pair("$mainUrl/ajax/list/views?type=week", "Trending by week"),
|
||||
Pair("$mainUrl/ajax/list/views?type=month", "Trending by month"),
|
||||
)
|
||||
|
||||
val items = urls.mapNotNull {
|
||||
suspendSafeApiCall {
|
||||
val home = Jsoup.parse(
|
||||
parseJson<Response>(
|
||||
app.get(
|
||||
it.first
|
||||
).text
|
||||
).html
|
||||
).select("div.flw-item").mapNotNull secondMap@ {
|
||||
val title = it.selectFirst("h3.title a")?.text() ?: return@secondMap null
|
||||
val link = it.selectFirst("a")?.attr("href") ?: return@secondMap null
|
||||
val poster = it.selectFirst("img.lazyload")?.attr("data-src")
|
||||
AnimeSearchResponse(
|
||||
title,
|
||||
link,
|
||||
this.name,
|
||||
TvType.Anime,
|
||||
poster,
|
||||
null,
|
||||
if (title.contains("(DUB)") || title.contains("(Dub)")) EnumSet.of(
|
||||
DubStatus.Dubbed
|
||||
) else EnumSet.of(DubStatus.Subbed),
|
||||
)
|
||||
}
|
||||
HomePageList(name, home)
|
||||
}
|
||||
}
|
||||
|
||||
if (items.isEmpty()) throw ErrorLoadingException()
|
||||
return HomePageResponse(items)
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
return app.get("$mainUrl/search/?keyword=$query").document.select("div.flw-item")
|
||||
.mapNotNull {
|
||||
val title = it.selectFirst("h3 a")?.text() ?: ""
|
||||
val url = it.selectFirst("a.film-poster-ahref")?.attr("href")
|
||||
?.replace("watch/", "anime/")?.replace(
|
||||
Regex("(-episode-(\\d+)/\$|-episode-(\\d+)\$|-episode-full|-episode-.*-.(/|))"),
|
||||
""
|
||||
) ?: return@mapNotNull null
|
||||
val poster = it.selectFirst(".film-poster img")?.attr("data-src")
|
||||
AnimeSearchResponse(
|
||||
title,
|
||||
url,
|
||||
this.name,
|
||||
TvType.Anime,
|
||||
poster,
|
||||
null,
|
||||
if (title.contains("(DUB)") || title.contains("(Dub)")) EnumSet.of(
|
||||
DubStatus.Dubbed
|
||||
) else EnumSet.of(DubStatus.Subbed),
|
||||
)
|
||||
}.toList()
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val doc = app.get(url, timeout = 120).document
|
||||
val poster = doc.selectFirst(".mb-2 img")?.attr("src")
|
||||
?: doc.selectFirst("head meta[property=og:image]")?.attr("content")
|
||||
val title = doc.selectFirst("h1.heading-name a")!!.text()
|
||||
val description = doc.selectFirst("div.description p")?.text()?.trim()
|
||||
val genres = doc.select("div.row-line a").map { it.text() }
|
||||
val test = if (doc.selectFirst("div.dp-i-c-right").toString()
|
||||
.contains("Airing")
|
||||
) ShowStatus.Ongoing else ShowStatus.Completed
|
||||
val episodes = doc.select("div.tab-content ul li.nav-item").mapNotNull {
|
||||
val link = it.selectFirst("a")?.attr("href") ?: return@mapNotNull null
|
||||
Episode(link)
|
||||
}
|
||||
val type = if (doc.selectFirst(".dp-i-stats").toString()
|
||||
.contains("Movies")
|
||||
) TvType.AnimeMovie else TvType.Anime
|
||||
return newAnimeLoadResponse(title, url, type) {
|
||||
posterUrl = poster
|
||||
addEpisodes(DubStatus.Subbed, episodes)
|
||||
showStatus = test
|
||||
plot = description
|
||||
tags = genres
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
app.get(data).document.select("#servers-list ul.nav li a").apmap {
|
||||
val server = it.attr("data-embed")
|
||||
loadExtractor(server, data, subtitleCallback, callback)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -1,270 +0,0 @@
|
|||
package com.lagradost.cloudstream3.animeproviders
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.APIHolder.unixTime
|
||||
import com.lagradost.cloudstream3.APIHolder.unixTimeMS
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||
import org.jsoup.Jsoup
|
||||
import java.util.*
|
||||
|
||||
class DubbedAnimeProvider : MainAPI() {
|
||||
override var mainUrl = "https://bestdubbedanime.com"
|
||||
override var name = "DubbedAnime"
|
||||
override val hasQuickSearch = true
|
||||
override val hasMainPage = true
|
||||
|
||||
override val supportedTypes = setOf(
|
||||
TvType.AnimeMovie,
|
||||
TvType.Anime,
|
||||
)
|
||||
|
||||
data class QueryEpisodeResultRoot(
|
||||
@JsonProperty("result")
|
||||
val result: QueryEpisodeResult,
|
||||
)
|
||||
|
||||
data class QueryEpisodeResult(
|
||||
@JsonProperty("anime") val anime: List<EpisodeInfo>,
|
||||
@JsonProperty("error") val error: Boolean,
|
||||
@JsonProperty("errorMSG") val errorMSG: String?,
|
||||
)
|
||||
|
||||
data class EpisodeInfo(
|
||||
@JsonProperty("serversHTML") val serversHTML: String,
|
||||
@JsonProperty("title") val title: String,
|
||||
@JsonProperty("preview_img") val previewImg: String?,
|
||||
@JsonProperty("wideImg") val wideImg: String?,
|
||||
@JsonProperty("year") val year: String?,
|
||||
@JsonProperty("desc") val desc: String?,
|
||||
|
||||
/*
|
||||
@JsonProperty("rowid") val rowid: String,
|
||||
@JsonProperty("status") val status: String,
|
||||
@JsonProperty("skips") val skips: String,
|
||||
@JsonProperty("totalEp") val totalEp: Long,
|
||||
@JsonProperty("ep") val ep: String,
|
||||
@JsonProperty("NextEp") val nextEp: Long,
|
||||
@JsonProperty("slug") val slug: String,
|
||||
@JsonProperty("showid") val showid: String,
|
||||
@JsonProperty("Epviews") val epviews: String,
|
||||
@JsonProperty("TotalViews") val totalViews: String,
|
||||
@JsonProperty("tags") val tags: String,*/
|
||||
)
|
||||
|
||||
private suspend fun parseDocumentTrending(url: String): List<SearchResponse> {
|
||||
val response = app.get(url).text
|
||||
val document = Jsoup.parse(response)
|
||||
return document.select("li > a").mapNotNull {
|
||||
val href = fixUrl(it.attr("href"))
|
||||
val title = it.selectFirst("> div > div.cittx")?.text() ?: return@mapNotNull null
|
||||
val poster = fixUrlNull(it.selectFirst("> div > div.imghddde > img")?.attr("src"))
|
||||
AnimeSearchResponse(
|
||||
title,
|
||||
href,
|
||||
this.name,
|
||||
TvType.Anime,
|
||||
poster,
|
||||
null,
|
||||
EnumSet.of(DubStatus.Dubbed),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun parseDocument(
|
||||
url: String,
|
||||
trimEpisode: Boolean = false
|
||||
): List<SearchResponse> {
|
||||
val response = app.get(url).text
|
||||
val document = Jsoup.parse(response)
|
||||
return document.select("a.grid__link").mapNotNull {
|
||||
val href = fixUrl(it.attr("href"))
|
||||
val title = it.selectFirst("> div.gridtitlek")?.text() ?: return@mapNotNull null
|
||||
val poster =
|
||||
fixUrl(it.selectFirst("> img.grid__img")?.attr("src") ?: return@mapNotNull null)
|
||||
AnimeSearchResponse(
|
||||
title,
|
||||
if (trimEpisode) href.removeRange(href.lastIndexOf('/'), href.length) else href,
|
||||
this.name,
|
||||
TvType.Anime,
|
||||
poster,
|
||||
null,
|
||||
EnumSet.of(DubStatus.Dubbed),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse {
|
||||
val trendingUrl = "$mainUrl/xz/trending.php?_=$unixTimeMS"
|
||||
val lastEpisodeUrl = "$mainUrl/xz/epgrid.php?p=1&_=$unixTimeMS"
|
||||
val recentlyAddedUrl = "$mainUrl/xz/gridgrabrecent.php?p=1&_=$unixTimeMS"
|
||||
//val allUrl = "$mainUrl/xz/gridgrab.php?p=1&limit=12&_=$unixTimeMS"
|
||||
|
||||
val listItems = listOf(
|
||||
HomePageList("Trending", parseDocumentTrending(trendingUrl)),
|
||||
HomePageList("Recently Added", parseDocument(recentlyAddedUrl)),
|
||||
HomePageList("Recent Releases", parseDocument(lastEpisodeUrl, true)),
|
||||
// HomePageList("All", parseDocument(allUrl))
|
||||
)
|
||||
|
||||
return HomePageResponse(listItems)
|
||||
}
|
||||
|
||||
|
||||
private suspend fun getEpisode(slug: String, isMovie: Boolean): EpisodeInfo {
|
||||
val url =
|
||||
mainUrl + (if (isMovie) "/movies/jsonMovie" else "/xz/v3/jsonEpi") + ".php?slug=$slug&_=$unixTime"
|
||||
val response = app.get(url).text
|
||||
val mapped = parseJson<QueryEpisodeResultRoot>(response)
|
||||
return mapped.result.anime.first()
|
||||
}
|
||||
|
||||
|
||||
private fun getIsMovie(href: String): Boolean {
|
||||
return href.contains("movies/")
|
||||
}
|
||||
|
||||
private fun getSlug(href: String): String {
|
||||
return href.replace("$mainUrl/", "")
|
||||
}
|
||||
|
||||
override suspend fun quickSearch(query: String): List<SearchResponse> {
|
||||
val url = "$mainUrl/xz/searchgrid.php?p=1&limit=12&s=$query&_=$unixTime"
|
||||
val response = app.get(url).text
|
||||
val document = Jsoup.parse(response)
|
||||
val items = document.select("div.grid__item > a")
|
||||
if (items.isEmpty()) return emptyList()
|
||||
return items.mapNotNull { i ->
|
||||
val href = fixUrl(i.attr("href"))
|
||||
val title = i.selectFirst("div.gridtitlek")?.text() ?: return@mapNotNull null
|
||||
val img = fixUrlNull(i.selectFirst("img.grid__img")?.attr("src"))
|
||||
|
||||
if (getIsMovie(href)) {
|
||||
MovieSearchResponse(
|
||||
title, href, this.name, TvType.AnimeMovie, img, null
|
||||
)
|
||||
} else {
|
||||
AnimeSearchResponse(
|
||||
title,
|
||||
href,
|
||||
this.name,
|
||||
TvType.Anime,
|
||||
img,
|
||||
null,
|
||||
EnumSet.of(DubStatus.Dubbed),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
val url = "$mainUrl/search/$query"
|
||||
val response = app.get(url).text
|
||||
val document = Jsoup.parse(response)
|
||||
val items = document.select("div.resultinner > a.resulta")
|
||||
if (items.isEmpty()) return ArrayList()
|
||||
return items.mapNotNull { i ->
|
||||
val innerDiv = i.selectFirst("> div.result")
|
||||
val href = fixUrl(i.attr("href"))
|
||||
val img = fixUrl(innerDiv?.selectFirst("> div.imgkz > img")?.attr("src") ?: return@mapNotNull null)
|
||||
val title = innerDiv.selectFirst("> div.titleresults")?.text() ?: return@mapNotNull null
|
||||
|
||||
if (getIsMovie(href)) {
|
||||
MovieSearchResponse(
|
||||
title, href, this.name, TvType.AnimeMovie, img, null
|
||||
)
|
||||
} else {
|
||||
AnimeSearchResponse(
|
||||
title,
|
||||
href,
|
||||
this.name,
|
||||
TvType.Anime,
|
||||
img,
|
||||
null,
|
||||
EnumSet.of(DubStatus.Dubbed),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
val serversHTML = (if (data.startsWith(mainUrl)) { // CLASSIC EPISODE
|
||||
val slug = getSlug(data)
|
||||
getEpisode(slug, false).serversHTML
|
||||
} else data).replace("\\", "")
|
||||
|
||||
val hls = ArrayList("hl=\"(.*?)\"".toRegex().findAll(serversHTML).map {
|
||||
it.groupValues[1]
|
||||
}.toList())
|
||||
for (hl in hls) {
|
||||
try {
|
||||
val sources = app.get("$mainUrl/xz/api/playeri.php?url=$hl&_=$unixTime").text
|
||||
val find = "src=\"(.*?)\".*?label=\"(.*?)\"".toRegex().find(sources)
|
||||
if (find != null) {
|
||||
val quality = find.groupValues[2]
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
this.name,
|
||||
this.name + " " + quality + if (quality.endsWith('p')) "" else 'p',
|
||||
fixUrl(find.groupValues[1]),
|
||||
this.mainUrl,
|
||||
getQualityFromName(quality)
|
||||
)
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
//IDK
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
if (getIsMovie(url)) {
|
||||
val realSlug = url.replace("movies/", "")
|
||||
val episode = getEpisode(realSlug, true)
|
||||
val poster = episode.previewImg ?: episode.wideImg
|
||||
return MovieLoadResponse(
|
||||
episode.title,
|
||||
realSlug,
|
||||
this.name,
|
||||
TvType.AnimeMovie,
|
||||
episode.serversHTML,
|
||||
if (poster == null) null else fixUrl(poster),
|
||||
episode.year?.toIntOrNull(),
|
||||
episode.desc,
|
||||
null
|
||||
)
|
||||
} else {
|
||||
val response = app.get(url).text
|
||||
val document = Jsoup.parse(response)
|
||||
val title = document.selectFirst("h4")!!.text()
|
||||
val descriptHeader = document.selectFirst("div.animeDescript")
|
||||
val descript = descriptHeader?.selectFirst("> p")?.text()
|
||||
val year = descriptHeader?.selectFirst("> div.distatsx > div.sroverd")
|
||||
?.text()
|
||||
?.replace("Released: ", "")
|
||||
?.toIntOrNull()
|
||||
|
||||
val episodes = document.select("a.epibloks").map {
|
||||
val epTitle = it.selectFirst("> div.inwel > span.isgrxx")?.text()
|
||||
Episode(fixUrl(it.attr("href")), epTitle)
|
||||
}
|
||||
|
||||
val img = fixUrl(document.select("div.fkimgs > img").attr("src"))
|
||||
return newAnimeLoadResponse(title, url, TvType.Anime) {
|
||||
posterUrl = img
|
||||
this.year = year
|
||||
addEpisodes(DubStatus.Dubbed, episodes)
|
||||
plot = descript
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,412 +0,0 @@
|
|||
package com.lagradost.cloudstream3.animeproviders
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||
import com.lagradost.cloudstream3.utils.AppUtils
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Document
|
||||
import java.net.URI
|
||||
import java.util.*
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
class GogoanimeProvider : MainAPI() {
|
||||
companion object {
|
||||
fun getType(t: String): TvType {
|
||||
return if (t.contains("OVA") || t.contains("Special")) TvType.OVA
|
||||
else if (t.contains("Movie")) TvType.AnimeMovie
|
||||
else TvType.Anime
|
||||
}
|
||||
|
||||
fun getStatus(t: String): ShowStatus {
|
||||
return when (t) {
|
||||
"Completed" -> ShowStatus.Completed
|
||||
"Ongoing" -> ShowStatus.Ongoing
|
||||
else -> ShowStatus.Completed
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param id base64Decode(show_id) + IV
|
||||
* @return the encryption key
|
||||
* */
|
||||
private fun getKey(id: String): String? {
|
||||
return normalSafeApiCall {
|
||||
id.map {
|
||||
it.code.toString(16)
|
||||
}.joinToString("").substring(0, 32)
|
||||
}
|
||||
}
|
||||
|
||||
val qualityRegex = Regex("(\\d+)P")
|
||||
|
||||
// https://github.com/saikou-app/saikou/blob/3e756bd8e876ad7a9318b17110526880525a5cd3/app/src/main/java/ani/saikou/anime/source/extractors/GogoCDN.kt#L60
|
||||
// No Licence on the function
|
||||
private fun cryptoHandler(
|
||||
string: String,
|
||||
iv: String,
|
||||
secretKeyString: String,
|
||||
encrypt: Boolean = true
|
||||
): String {
|
||||
//println("IV: $iv, Key: $secretKeyString, encrypt: $encrypt, Message: $string")
|
||||
val ivParameterSpec = IvParameterSpec(iv.toByteArray())
|
||||
val secretKey = SecretKeySpec(secretKeyString.toByteArray(), "AES")
|
||||
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
|
||||
return if (!encrypt) {
|
||||
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec)
|
||||
String(cipher.doFinal(base64DecodeArray(string)))
|
||||
} else {
|
||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec)
|
||||
base64Encode(cipher.doFinal(string.toByteArray()))
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.decodeHex(): ByteArray {
|
||||
check(length % 2 == 0) { "Must have an even length" }
|
||||
return chunked(2)
|
||||
.map { it.toInt(16).toByte() }
|
||||
.toByteArray()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param iframeUrl something like https://gogoplay4.com/streaming.php?id=XXXXXX
|
||||
* @param mainApiName used for ExtractorLink names and source
|
||||
* @param iv secret iv from site, required non-null if isUsingAdaptiveKeys is off
|
||||
* @param secretKey secret key for decryption from site, required non-null if isUsingAdaptiveKeys is off
|
||||
* @param secretDecryptKey secret key to decrypt the response json, required non-null if isUsingAdaptiveKeys is off
|
||||
* @param isUsingAdaptiveKeys generates keys from IV and ID, see getKey()
|
||||
* @param isUsingAdaptiveData generate encrypt-ajax data based on $("script[data-name='episode']")[0].dataset.value
|
||||
* */
|
||||
suspend fun extractVidstream(
|
||||
iframeUrl: String,
|
||||
mainApiName: String,
|
||||
callback: (ExtractorLink) -> Unit,
|
||||
iv: String?,
|
||||
secretKey: String?,
|
||||
secretDecryptKey: String?,
|
||||
// This could be removed, but i prefer it verbose
|
||||
isUsingAdaptiveKeys: Boolean,
|
||||
isUsingAdaptiveData: Boolean,
|
||||
// If you don't want to re-fetch the document
|
||||
iframeDocument: Document? = null
|
||||
) = safeApiCall {
|
||||
// https://github.com/saikou-app/saikou/blob/3e756bd8e876ad7a9318b17110526880525a5cd3/app/src/main/java/ani/saikou/anime/source/extractors/GogoCDN.kt
|
||||
// No Licence on the following code
|
||||
// Also modified of https://github.com/jmir1/aniyomi-extensions/blob/master/src/en/gogoanime/src/eu/kanade/tachiyomi/animeextension/en/gogoanime/extractors/GogoCdnExtractor.kt
|
||||
// License on the code above https://github.com/jmir1/aniyomi-extensions/blob/master/LICENSE
|
||||
|
||||
if ((iv == null || secretKey == null || secretDecryptKey == null) && !isUsingAdaptiveKeys)
|
||||
return@safeApiCall
|
||||
|
||||
val id = Regex("id=([^&]+)").find(iframeUrl)!!.value.removePrefix("id=")
|
||||
|
||||
var document: Document? = iframeDocument
|
||||
val foundIv =
|
||||
iv ?: (document ?: app.get(iframeUrl).document.also { document = it })
|
||||
.select("""div.wrapper[class*=container]""")
|
||||
.attr("class").split("-").lastOrNull() ?: return@safeApiCall
|
||||
val foundKey = secretKey ?: getKey(base64Decode(id) + foundIv) ?: return@safeApiCall
|
||||
val foundDecryptKey = secretDecryptKey ?: foundKey
|
||||
|
||||
val uri = URI(iframeUrl)
|
||||
val mainUrl = "https://" + uri.host
|
||||
|
||||
val encryptedId = cryptoHandler(id, foundIv, foundKey)
|
||||
val encryptRequestData = if (isUsingAdaptiveData) {
|
||||
// Only fetch the document if necessary
|
||||
val realDocument = document ?: app.get(iframeUrl).document
|
||||
val dataEncrypted =
|
||||
realDocument.select("script[data-name='episode']").attr("data-value")
|
||||
val headers = cryptoHandler(dataEncrypted, foundIv, foundKey, false)
|
||||
"id=$encryptedId&alias=$id&" + headers.substringAfter("&")
|
||||
} else {
|
||||
"id=$encryptedId&alias=$id"
|
||||
}
|
||||
|
||||
val jsonResponse =
|
||||
app.get(
|
||||
"$mainUrl/encrypt-ajax.php?$encryptRequestData",
|
||||
headers = mapOf("X-Requested-With" to "XMLHttpRequest")
|
||||
)
|
||||
val dataencrypted =
|
||||
jsonResponse.text.substringAfter("{\"data\":\"").substringBefore("\"}")
|
||||
val datadecrypted = cryptoHandler(dataencrypted, foundIv, foundDecryptKey, false)
|
||||
val sources = AppUtils.parseJson<GogoSources>(datadecrypted)
|
||||
|
||||
fun invokeGogoSource(
|
||||
source: GogoSource,
|
||||
sourceCallback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
sourceCallback.invoke(
|
||||
ExtractorLink(
|
||||
mainApiName,
|
||||
mainApiName,
|
||||
source.file,
|
||||
mainUrl,
|
||||
getQualityFromName(source.label),
|
||||
isM3u8 = source.type == "hls" || source.label?.contains(
|
||||
"auto",
|
||||
ignoreCase = true
|
||||
) == true
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
sources.source?.forEach {
|
||||
invokeGogoSource(it, callback)
|
||||
}
|
||||
sources.sourceBk?.forEach {
|
||||
invokeGogoSource(it, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override var mainUrl = "https://gogoanime.lu"
|
||||
override var name = "GogoAnime"
|
||||
override val hasQuickSearch = false
|
||||
override val hasMainPage = true
|
||||
|
||||
override val supportedTypes = setOf(
|
||||
TvType.AnimeMovie,
|
||||
TvType.Anime,
|
||||
TvType.OVA
|
||||
)
|
||||
|
||||
val headers = mapOf(
|
||||
"authority" to "ajax.gogo-load.com",
|
||||
"sec-ch-ua" to "\"Google Chrome\";v=\"89\", \"Chromium\";v=\"89\", \";Not A Brand\";v=\"99\"",
|
||||
"accept" to "text/html, */*; q=0.01",
|
||||
"dnt" to "1",
|
||||
"sec-ch-ua-mobile" to "?0",
|
||||
"user-agent" to "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36",
|
||||
"origin" to mainUrl,
|
||||
"sec-fetch-site" to "cross-site",
|
||||
"sec-fetch-mode" to "cors",
|
||||
"sec-fetch-dest" to "empty",
|
||||
"referer" to "$mainUrl/"
|
||||
)
|
||||
val parseRegex =
|
||||
Regex("""<li>\s*\n.*\n.*<a\s*href=["'](.*?-episode-(\d+))["']\s*title=["'](.*?)["']>\n.*?img src="(.*?)"""")
|
||||
|
||||
override val mainPage = mainPageOf(
|
||||
Pair("1", "Recent Release - Sub"),
|
||||
Pair("2", "Recent Release - Dub"),
|
||||
Pair("3", "Recent Release - Chinese"),
|
||||
)
|
||||
|
||||
override suspend fun getMainPage(
|
||||
page: Int,
|
||||
request : MainPageRequest
|
||||
): HomePageResponse {
|
||||
val params = mapOf("page" to page.toString(), "type" to request.data)
|
||||
val html = app.get(
|
||||
"https://ajax.gogo-load.com/ajax/page-recent-release.html",
|
||||
headers = headers,
|
||||
params = params
|
||||
)
|
||||
val isSub = listOf(1, 3).contains(request.data.toInt())
|
||||
|
||||
val home = parseRegex.findAll(html.text).map {
|
||||
val (link, epNum, title, poster) = it.destructured
|
||||
newAnimeSearchResponse(title, link) {
|
||||
this.posterUrl = poster
|
||||
addDubStatus(!isSub, epNum.toIntOrNull())
|
||||
}
|
||||
}.toList()
|
||||
|
||||
return newHomePageResponse(request.name, home)
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): ArrayList<SearchResponse> {
|
||||
val link = "$mainUrl/search.html?keyword=$query"
|
||||
val html = app.get(link).text
|
||||
val doc = Jsoup.parse(html)
|
||||
|
||||
val episodes = doc.select(""".last_episodes li""").mapNotNull {
|
||||
AnimeSearchResponse(
|
||||
it.selectFirst(".name")?.text()?.replace(" (Dub)", "") ?: return@mapNotNull null,
|
||||
fixUrl(it.selectFirst(".name > a")?.attr("href") ?: return@mapNotNull null),
|
||||
this.name,
|
||||
TvType.Anime,
|
||||
it.selectFirst("img")?.attr("src"),
|
||||
it.selectFirst(".released")?.text()?.split(":")?.getOrNull(1)?.trim()
|
||||
?.toIntOrNull(),
|
||||
if (it.selectFirst(".name")?.text()
|
||||
?.contains("Dub") == true
|
||||
) EnumSet.of(DubStatus.Dubbed) else EnumSet.of(
|
||||
DubStatus.Subbed
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
return ArrayList(episodes)
|
||||
}
|
||||
|
||||
private fun getProperAnimeLink(uri: String): String {
|
||||
if (uri.contains("-episode")) {
|
||||
val split = uri.split("/")
|
||||
val slug = split[split.size - 1].split("-episode")[0]
|
||||
return "$mainUrl/category/$slug"
|
||||
}
|
||||
return uri
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val link = getProperAnimeLink(url)
|
||||
val episodeloadApi = "https://ajax.gogo-load.com/ajax/load-list-episode"
|
||||
val doc = app.get(link).document
|
||||
|
||||
val animeBody = doc.selectFirst(".anime_info_body_bg")
|
||||
val title = animeBody?.selectFirst("h1")!!.text()
|
||||
val poster = animeBody.selectFirst("img")?.attr("src")
|
||||
var description: String? = null
|
||||
val genre = ArrayList<String>()
|
||||
var year: Int? = null
|
||||
var status: String? = null
|
||||
var nativeName: String? = null
|
||||
var type: String? = null
|
||||
|
||||
animeBody.select("p.type").forEach { pType ->
|
||||
when (pType.selectFirst("span")?.text()?.trim()) {
|
||||
"Plot Summary:" -> {
|
||||
description = pType.text().replace("Plot Summary:", "").trim()
|
||||
}
|
||||
"Genre:" -> {
|
||||
genre.addAll(pType.select("a").map {
|
||||
it.attr("title")
|
||||
})
|
||||
}
|
||||
"Released:" -> {
|
||||
year = pType.text().replace("Released:", "").trim().toIntOrNull()
|
||||
}
|
||||
"Status:" -> {
|
||||
status = pType.text().replace("Status:", "").trim()
|
||||
}
|
||||
"Other name:" -> {
|
||||
nativeName = pType.text().replace("Other name:", "").trim()
|
||||
}
|
||||
"Type:" -> {
|
||||
type = pType.text().replace("type:", "").trim()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val animeId = doc.selectFirst("#movie_id")!!.attr("value")
|
||||
val params = mapOf("ep_start" to "0", "ep_end" to "2000", "id" to animeId)
|
||||
|
||||
val episodes = app.get(episodeloadApi, params = params).document.select("a").map {
|
||||
Episode(
|
||||
fixUrl(it.attr("href").trim()),
|
||||
"Episode " + it.selectFirst(".name")?.text()?.replace("EP", "")?.trim()
|
||||
)
|
||||
}.reversed()
|
||||
|
||||
return newAnimeLoadResponse(title, link, getType(type.toString())) {
|
||||
japName = nativeName
|
||||
engName = title
|
||||
posterUrl = poster
|
||||
this.year = year
|
||||
addEpisodes(DubStatus.Subbed, episodes) // TODO CHECK
|
||||
plot = description
|
||||
tags = genre
|
||||
|
||||
showStatus = getStatus(status.toString())
|
||||
}
|
||||
}
|
||||
|
||||
data class GogoSources(
|
||||
@JsonProperty("source") val source: List<GogoSource>?,
|
||||
@JsonProperty("sourceBk") val sourceBk: List<GogoSource>?,
|
||||
//val track: List<Any?>,
|
||||
//val advertising: List<Any?>,
|
||||
//val linkiframe: String
|
||||
)
|
||||
|
||||
data class GogoSource(
|
||||
@JsonProperty("file") val file: String,
|
||||
@JsonProperty("label") val label: String?,
|
||||
@JsonProperty("type") val type: String?,
|
||||
@JsonProperty("default") val default: String? = null
|
||||
)
|
||||
|
||||
private suspend fun extractVideos(
|
||||
uri: String,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
val doc = app.get(uri).document
|
||||
|
||||
val iframe = fixUrlNull(doc.selectFirst("div.play-video > iframe")?.attr("src")) ?: return
|
||||
|
||||
argamap(
|
||||
{
|
||||
val link = iframe.replace("streaming.php", "download")
|
||||
val page = app.get(link, headers = mapOf("Referer" to iframe))
|
||||
|
||||
page.document.select(".dowload > a").apmap {
|
||||
if (it.hasAttr("download")) {
|
||||
val qual = if (it.text()
|
||||
.contains("HDP")
|
||||
) "1080" else qualityRegex.find(it.text())?.destructured?.component1()
|
||||
.toString()
|
||||
callback(
|
||||
ExtractorLink(
|
||||
"Gogoanime",
|
||||
"Gogoanime",
|
||||
it.attr("href"),
|
||||
page.url,
|
||||
getQualityFromName(qual),
|
||||
it.attr("href").contains(".m3u8")
|
||||
)
|
||||
)
|
||||
} else {
|
||||
val url = it.attr("href")
|
||||
loadExtractor(url, null, subtitleCallback, callback)
|
||||
}
|
||||
}
|
||||
}, {
|
||||
val streamingResponse = app.get(iframe, headers = mapOf("Referer" to iframe))
|
||||
val streamingDocument = streamingResponse.document
|
||||
argamap({
|
||||
streamingDocument.select(".list-server-items > .linkserver")
|
||||
.forEach { element ->
|
||||
val status = element.attr("data-status") ?: return@forEach
|
||||
if (status != "1") return@forEach
|
||||
val data = element.attr("data-video") ?: return@forEach
|
||||
loadExtractor(data, streamingResponse.url, subtitleCallback, callback)
|
||||
}
|
||||
}, {
|
||||
val iv = "3134003223491201"
|
||||
val secretKey = "37911490979715163134003223491201"
|
||||
val secretDecryptKey = "54674138327930866480207815084989"
|
||||
extractVidstream(
|
||||
iframe,
|
||||
this.name,
|
||||
callback,
|
||||
iv,
|
||||
secretKey,
|
||||
secretDecryptKey,
|
||||
isUsingAdaptiveKeys = false,
|
||||
isUsingAdaptiveData = true
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
extractVideos(data, subtitleCallback, callback)
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -1,232 +0,0 @@
|
|||
package com.lagradost.cloudstream3.animeproviders
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.M3u8Helper
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
import org.jsoup.Jsoup
|
||||
|
||||
class GomunimeProvider : MainAPI() {
|
||||
override var mainUrl = "https://185.231.223.76"
|
||||
override var name = "Gomunime"
|
||||
override val hasMainPage = true
|
||||
override var lang = "id"
|
||||
override val hasDownloadSupport = true
|
||||
|
||||
override val supportedTypes = setOf(
|
||||
TvType.Anime,
|
||||
TvType.AnimeMovie,
|
||||
TvType.OVA
|
||||
)
|
||||
|
||||
companion object {
|
||||
fun getType(t: String): TvType {
|
||||
return if (t.contains("OVA") || t.contains("Special")) TvType.OVA
|
||||
else if (t.contains("Movie")) TvType.AnimeMovie
|
||||
else TvType.Anime
|
||||
}
|
||||
|
||||
fun getStatus(t: String): ShowStatus {
|
||||
return when (t) {
|
||||
"Completed" -> ShowStatus.Completed
|
||||
"Ongoing" -> ShowStatus.Ongoing
|
||||
else -> ShowStatus.Completed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val mainPage = mainPageOf(
|
||||
"e" to "Episode Baru",
|
||||
"c" to "Completed",
|
||||
"la" to "Live Action",
|
||||
"t" to "Trending"
|
||||
)
|
||||
|
||||
override suspend fun getMainPage(
|
||||
page: Int,
|
||||
request: MainPageRequest
|
||||
): HomePageResponse {
|
||||
val home = Jsoup.parse(
|
||||
(app.post(
|
||||
url = "$mainUrl/wp-admin/admin-ajax.php/wp-admin/admin-ajax.php",
|
||||
headers = mapOf("Referer" to mainUrl),
|
||||
data = mapOf(
|
||||
"action" to "home_ajax",
|
||||
"fungsi" to request.data,
|
||||
"pag" to "$page"
|
||||
)
|
||||
).parsedSafe<Response>()?.html ?: throw ErrorLoadingException("Invalid Json reponse"))
|
||||
).select("li").mapNotNull {
|
||||
val title = it.selectFirst("a.name")?.text()?.trim() ?: return@mapNotNull null
|
||||
val href = getProperAnimeLink(it.selectFirst("a")!!.attr("href"))
|
||||
val posterUrl = it.selectFirst("img")?.attr("src")
|
||||
val type = getType(it.selectFirst(".taglist > span")!!.text().trim())
|
||||
val epNum = it.select(".tag.ep").text().replace(Regex("[^0-9]"), "").trim()
|
||||
.toIntOrNull()
|
||||
newAnimeSearchResponse(title, href, type) {
|
||||
this.posterUrl = posterUrl
|
||||
addSub(epNum)
|
||||
}
|
||||
}
|
||||
|
||||
return newHomePageResponse(request.name, home)
|
||||
}
|
||||
|
||||
private fun getProperAnimeLink(uri: String): String {
|
||||
return if (uri.contains("-episode")) {
|
||||
val href =
|
||||
"$mainUrl/anime/" + Regex("\\w\\d/(.*)-episode.*").find(uri)?.groupValues?.get(1)
|
||||
.toString()
|
||||
when {
|
||||
href.contains("pokemon") -> href.replace(Regex("-[0-9]+"), "")
|
||||
else -> href
|
||||
}
|
||||
} else {
|
||||
uri
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
val link = "$mainUrl/?s=$query"
|
||||
val document = app.get(link).document
|
||||
|
||||
return document.select(".anime-list > li").map {
|
||||
val title = it.selectFirst("a.name")!!.text()
|
||||
val poster = it.selectFirst("img")!!.attr("src")
|
||||
val tvType = getType(it.selectFirst(".taglist > span")?.text().toString())
|
||||
val href = fixUrl(it.selectFirst("a.name")!!.attr("href"))
|
||||
|
||||
newAnimeSearchResponse(title, href, tvType) {
|
||||
this.posterUrl = poster
|
||||
addDubStatus(dubExist = false, subExist = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val document = app.get(url).document
|
||||
|
||||
val title = document.selectFirst(".entry-title")?.text().toString()
|
||||
val poster = document.selectFirst(".thumbposter > img")?.attr("data-lazy-src")
|
||||
val tags = document.select(".genxed > a").map { it.text() }
|
||||
|
||||
val year = Regex("\\d, ([0-9]*)").find(
|
||||
document.select("time[itemprop = datePublished]").text()
|
||||
)?.groupValues?.get(1)?.toIntOrNull()
|
||||
val status = getStatus(document.selectFirst(".spe > span")!!.ownText())
|
||||
val description = document.select("div[itemprop = description] > p").text()
|
||||
val trailer = document.selectFirst("div.embed-responsive noscript iframe")?.attr("src")
|
||||
val episodes = parseJson<List<EpisodeElement>>(
|
||||
Regex("var episodelist = (\\[.*])").find(
|
||||
document.select(".bixbox.bxcl.epcheck > script").toString().trim()
|
||||
)?.groupValues?.get(1).toString().replace(Regex("""\\"""), "").trim()
|
||||
).map {
|
||||
val name =
|
||||
Regex("(Episode\\s?[0-9]+)").find(it.epTitle.toString())?.groupValues?.getOrNull(0)
|
||||
?: it.epTitle
|
||||
val link = it.epLink
|
||||
Episode(link, name)
|
||||
}.reversed()
|
||||
|
||||
return newAnimeLoadResponse(title, url, TvType.Anime) {
|
||||
engName = title
|
||||
posterUrl = poster
|
||||
this.year = year
|
||||
addEpisodes(DubStatus.Subbed, episodes)
|
||||
showStatus = status
|
||||
plot = description
|
||||
this.tags = tags
|
||||
addTrailer(trailer)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
val document = app.get(data).document
|
||||
|
||||
val scriptData = document.select("aside.sidebar > script").dataNodes().toString()
|
||||
val key = scriptData.substringAfter("var a_ray = '").substringBefore("';")
|
||||
val title = scriptData.substringAfter("var judul_postingan = \"").substringBefore("\";")
|
||||
|
||||
val sources: List<Pair<String, String>> = app.post(
|
||||
url = "https://path.gomuni.me/app/vapi.php",
|
||||
data = mapOf("data" to key, "judul" to title, "func" to "mirror")
|
||||
).document.select("div.gomunime-server-mirror").map {
|
||||
Pair(
|
||||
it.attr("data-vhash"),
|
||||
it.attr("data-type")
|
||||
)
|
||||
}
|
||||
|
||||
sources.apmap {
|
||||
safeApiCall {
|
||||
when {
|
||||
it.second.contains("frame") -> {
|
||||
loadExtractor(it.first, data, subtitleCallback, callback)
|
||||
}
|
||||
it.second.contains("hls") -> {
|
||||
app.post(
|
||||
url = "https://path.gomuni.me/app/vapi.php",
|
||||
data = mapOf("fid" to it.first, "func" to "hls")
|
||||
).text.let { link ->
|
||||
M3u8Helper.generateM3u8(
|
||||
this.name,
|
||||
link,
|
||||
"$mainUrl/",
|
||||
headers = mapOf("Origin" to mainUrl)
|
||||
).forEach(callback)
|
||||
}
|
||||
}
|
||||
it.second.contains("mp4") -> {
|
||||
app.post(
|
||||
url = "https://path.gomuni.me/app/vapi.php",
|
||||
data = mapOf("data" to it.first, "func" to "blogs")
|
||||
).parsed<List<MobiSource>>().map {
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
source = name,
|
||||
name = "Mobi SD",
|
||||
url = it.file,
|
||||
referer = "$mainUrl/",
|
||||
quality = Qualities.P360.value
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private data class Response(
|
||||
@JsonProperty("status") val status: Boolean,
|
||||
@JsonProperty("html") val html: String
|
||||
)
|
||||
|
||||
data class MobiSource(
|
||||
@JsonProperty("file") val file: String,
|
||||
@JsonProperty("label") val label: String,
|
||||
@JsonProperty("type") val type: String
|
||||
)
|
||||
|
||||
private data class EpisodeElement(
|
||||
@JsonProperty("data-index") val dataIndex: Long?,
|
||||
@JsonProperty("ep-num") val epNum: String?,
|
||||
@JsonProperty("ep-title") val epTitle: String?,
|
||||
@JsonProperty("ep-link") val epLink: String,
|
||||
@JsonProperty("ep-date") val epDate: String?
|
||||
)
|
||||
|
||||
}
|
|
@ -1,319 +0,0 @@
|
|||
package com.lagradost.cloudstream3.animeproviders
|
||||
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8
|
||||
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
import java.util.*
|
||||
|
||||
|
||||
class JKAnimeProvider : MainAPI() {
|
||||
companion object {
|
||||
fun getType(t: String): TvType {
|
||||
return if (t.contains("OVA") || t.contains("Especial")) TvType.OVA
|
||||
else if (t.contains("Pelicula")) TvType.AnimeMovie
|
||||
else TvType.Anime
|
||||
}
|
||||
}
|
||||
|
||||
override var mainUrl = "https://jkanime.net"
|
||||
override var name = "JKAnime"
|
||||
override var lang = "es"
|
||||
override val hasMainPage = true
|
||||
override val hasChromecastSupport = true
|
||||
override val hasDownloadSupport = true
|
||||
override val supportedTypes = setOf(
|
||||
TvType.AnimeMovie,
|
||||
TvType.OVA,
|
||||
TvType.Anime,
|
||||
)
|
||||
|
||||
override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse {
|
||||
val urls = listOf(
|
||||
Pair(
|
||||
"$mainUrl/directorio/?filtro=fecha&tipo=TV&estado=1&fecha=none&temporada=none&orden=desc",
|
||||
"En emisión"
|
||||
),
|
||||
Pair(
|
||||
"$mainUrl/directorio/?filtro=fecha&tipo=none&estado=none&fecha=none&temporada=none&orden=none",
|
||||
"Animes"
|
||||
),
|
||||
Pair(
|
||||
"$mainUrl/directorio/?filtro=fecha&tipo=Movie&estado=none&fecha=none&temporada=none&orden=none",
|
||||
"Películas"
|
||||
),
|
||||
)
|
||||
|
||||
val items = ArrayList<HomePageList>()
|
||||
|
||||
items.add(
|
||||
HomePageList(
|
||||
"Últimos episodios",
|
||||
app.get(mainUrl).document.select(".listadoanime-home a.bloqq").map {
|
||||
val title = it.selectFirst("h5")?.text()
|
||||
val dubstat = if (title!!.contains("Latino") || title.contains("Castellano"))
|
||||
DubStatus.Dubbed else DubStatus.Subbed
|
||||
val poster =
|
||||
it.selectFirst(".anime__sidebar__comment__item__pic img")?.attr("src") ?: ""
|
||||
val epRegex = Regex("/(\\d+)/|/especial/|/ova/")
|
||||
val url = it.attr("href").replace(epRegex, "")
|
||||
val epNum =
|
||||
it.selectFirst("h6")?.text()?.replace("Episodio ", "")?.toIntOrNull()
|
||||
newAnimeSearchResponse(title, url) {
|
||||
this.posterUrl = poster
|
||||
addDubStatus(dubstat, epNum)
|
||||
}
|
||||
})
|
||||
)
|
||||
urls.apmap { (url, name) ->
|
||||
val soup = app.get(url).document
|
||||
val home = soup.select(".g-0").map {
|
||||
val title = it.selectFirst("h5 a")?.text()
|
||||
val poster = it.selectFirst("img")?.attr("src") ?: ""
|
||||
AnimeSearchResponse(
|
||||
title!!,
|
||||
fixUrl(it.selectFirst("a")?.attr("href") ?: ""),
|
||||
this.name,
|
||||
TvType.Anime,
|
||||
fixUrl(poster),
|
||||
null,
|
||||
if (title.contains("Latino") || title.contains("Castellano")) EnumSet.of(
|
||||
DubStatus.Dubbed
|
||||
) else EnumSet.of(DubStatus.Subbed),
|
||||
)
|
||||
}
|
||||
items.add(HomePageList(name, home))
|
||||
}
|
||||
|
||||
if (items.size <= 0) throw ErrorLoadingException()
|
||||
return HomePageResponse(items)
|
||||
}
|
||||
|
||||
data class MainSearch(
|
||||
@JsonProperty("animes") val animes: List<Animes>,
|
||||
@JsonProperty("anime_types") val animeTypes: AnimeTypes
|
||||
)
|
||||
|
||||
data class Animes(
|
||||
@JsonProperty("id") val id: String,
|
||||
@JsonProperty("slug") val slug: String,
|
||||
@JsonProperty("title") val title: String,
|
||||
@JsonProperty("image") val image: String,
|
||||
@JsonProperty("synopsis") val synopsis: String,
|
||||
@JsonProperty("type") val type: String,
|
||||
@JsonProperty("status") val status: String,
|
||||
@JsonProperty("thumbnail") val thumbnail: String
|
||||
)
|
||||
|
||||
data class AnimeTypes(
|
||||
@JsonProperty("TV") val TV: String,
|
||||
@JsonProperty("OVA") val OVA: String,
|
||||
@JsonProperty("Movie") val Movie: String,
|
||||
@JsonProperty("Special") val Special: String,
|
||||
@JsonProperty("ONA") val ONA: String,
|
||||
@JsonProperty("Music") val Music: String
|
||||
)
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
val main = app.get("$mainUrl/ajax/ajax_search/?q=$query").text
|
||||
val json = parseJson<MainSearch>(main)
|
||||
return json.animes.map {
|
||||
val title = it.title
|
||||
val href = "$mainUrl/${it.slug}"
|
||||
val image = "https://cdn.jkanime.net/assets/images/animes/image/${it.slug}.jpg"
|
||||
AnimeSearchResponse(
|
||||
title,
|
||||
href,
|
||||
this.name,
|
||||
TvType.Anime,
|
||||
image,
|
||||
null,
|
||||
if (title.contains("Latino") || title.contains("Castellano")) EnumSet.of(
|
||||
DubStatus.Dubbed
|
||||
) else EnumSet.of(DubStatus.Subbed),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val doc = app.get(url, timeout = 120).document
|
||||
val poster = doc.selectFirst(".set-bg")?.attr("data-setbg")
|
||||
val title = doc.selectFirst(".anime__details__title > h3")?.text()
|
||||
val type = doc.selectFirst(".anime__details__text")?.text()
|
||||
val description = doc.selectFirst(".anime__details__text > p")?.text()
|
||||
val genres = doc.select("div.col-lg-6:nth-child(1) > ul:nth-child(1) > li:nth-child(2) > a")
|
||||
.map { it.text() }
|
||||
val status = when (doc.selectFirst("span.enemision")?.text()) {
|
||||
"En emisión" -> ShowStatus.Ongoing
|
||||
"Concluido" -> ShowStatus.Completed
|
||||
else -> null
|
||||
}
|
||||
val animeID = doc.selectFirst("div.ml-2")?.attr("data-anime")?.toInt()
|
||||
val animeeps = "$mainUrl/ajax/last_episode/$animeID/"
|
||||
val jsoneps = app.get(animeeps).text
|
||||
val lastepnum =
|
||||
jsoneps.substringAfter("{\"number\":\"").substringBefore("\",\"title\"").toInt()
|
||||
val episodes = (1..lastepnum).map {
|
||||
val link = "${url.removeSuffix("/")}/$it"
|
||||
Episode(link)
|
||||
}
|
||||
|
||||
return newAnimeLoadResponse(title!!, url, getType(type!!)) {
|
||||
posterUrl = poster
|
||||
addEpisodes(DubStatus.Subbed, episodes)
|
||||
showStatus = status
|
||||
plot = description
|
||||
tags = genres
|
||||
}
|
||||
}
|
||||
|
||||
data class Nozomi(
|
||||
@JsonProperty("file") val file: String?
|
||||
)
|
||||
|
||||
private fun streamClean(
|
||||
name: String,
|
||||
url: String,
|
||||
referer: String,
|
||||
quality: String?,
|
||||
callback: (ExtractorLink) -> Unit,
|
||||
m3u8: Boolean
|
||||
): Boolean {
|
||||
callback(
|
||||
ExtractorLink(
|
||||
name,
|
||||
name,
|
||||
url,
|
||||
referer,
|
||||
getQualityFromName(quality),
|
||||
m3u8
|
||||
)
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
app.get(data).document.select("script").apmap { script ->
|
||||
if (script.data().contains("var video = []")) {
|
||||
val videos = script.data().replace("\\/", "/")
|
||||
fetchUrls(videos).map {
|
||||
it.replace("$mainUrl/jkfembed.php?u=", "https://embedsito.com/v/")
|
||||
.replace("$mainUrl/jkokru.php?u=", "http://ok.ru/videoembed/")
|
||||
.replace("$mainUrl/jkvmixdrop.php?u=", "https://mixdrop.co/e/")
|
||||
.replace("$mainUrl/jk.php?u=", "$mainUrl/")
|
||||
}.apmap { link ->
|
||||
loadExtractor(link, data, subtitleCallback, callback)
|
||||
if (link.contains("um2.php")) {
|
||||
val doc = app.get(link, referer = data).document
|
||||
val gsplaykey = doc.select("form input[value]").attr("value")
|
||||
app.post(
|
||||
"$mainUrl/gsplay/redirect_post.php",
|
||||
headers = mapOf(
|
||||
"Host" to "jkanime.net",
|
||||
"User-Agent" to USER_AGENT,
|
||||
"Accept" to "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
||||
"Accept-Language" to "en-US,en;q=0.5",
|
||||
"Referer" to link,
|
||||
"Content-Type" to "application/x-www-form-urlencoded",
|
||||
"Origin" to "https://jkanime.net",
|
||||
"DNT" to "1",
|
||||
"Connection" to "keep-alive",
|
||||
"Upgrade-Insecure-Requests" to "1",
|
||||
"Sec-Fetch-Dest" to "iframe",
|
||||
"Sec-Fetch-Mode" to "navigate",
|
||||
"Sec-Fetch-Site" to "same-origin",
|
||||
"TE" to "trailers",
|
||||
"Pragma" to "no-cache",
|
||||
"Cache-Control" to "no-cache",
|
||||
),
|
||||
data = mapOf(Pair("data", gsplaykey)),
|
||||
allowRedirects = false
|
||||
).okhttpResponse.headers.values("location").apmap { loc ->
|
||||
val postkey = loc.replace("/gsplay/player.html#", "")
|
||||
val nozomitext = app.post(
|
||||
"$mainUrl/gsplay/api.php",
|
||||
headers = mapOf(
|
||||
"Host" to "jkanime.net",
|
||||
"User-Agent" to USER_AGENT,
|
||||
"Accept" to "application/json, text/javascript, */*; q=0.01",
|
||||
"Accept-Language" to "en-US,en;q=0.5",
|
||||
"Content-Type" to "application/x-www-form-urlencoded; charset=UTF-8",
|
||||
"X-Requested-With" to "XMLHttpRequest",
|
||||
"Origin" to "https://jkanime.net",
|
||||
"DNT" to "1",
|
||||
"Connection" to "keep-alive",
|
||||
"Sec-Fetch-Dest" to "empty",
|
||||
"Sec-Fetch-Mode" to "cors",
|
||||
"Sec-Fetch-Site" to "same-origin",
|
||||
),
|
||||
data = mapOf(Pair("v", postkey)),
|
||||
allowRedirects = false
|
||||
).text
|
||||
val json = parseJson<Nozomi>(nozomitext)
|
||||
val nozomiurl = listOf(json.file)
|
||||
if (nozomiurl.isEmpty()) null else
|
||||
nozomiurl.forEach { url ->
|
||||
val nozominame = "Nozomi"
|
||||
streamClean(
|
||||
nozominame,
|
||||
url!!,
|
||||
"",
|
||||
null,
|
||||
callback,
|
||||
url.contains(".m3u8")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (link.contains("um.php")) {
|
||||
val desutext = app.get(link, referer = data).text
|
||||
val desuRegex = Regex("((https:|http:)//.*\\.m3u8)")
|
||||
val file = desuRegex.find(desutext)?.value
|
||||
val namedesu = "Desu"
|
||||
generateM3u8(
|
||||
namedesu,
|
||||
file!!,
|
||||
mainUrl,
|
||||
).forEach { desurl ->
|
||||
streamClean(
|
||||
namedesu,
|
||||
desurl.url,
|
||||
mainUrl,
|
||||
desurl.quality.toString(),
|
||||
callback,
|
||||
true
|
||||
)
|
||||
}
|
||||
}
|
||||
if (link.contains("jkmedia")) {
|
||||
app.get(
|
||||
link,
|
||||
referer = data,
|
||||
allowRedirects = false
|
||||
).okhttpResponse.headers.values("location").apmap { xtremeurl ->
|
||||
val namex = "Xtreme S"
|
||||
streamClean(
|
||||
namex,
|
||||
xtremeurl,
|
||||
"",
|
||||
null,
|
||||
callback,
|
||||
xtremeurl.contains(".m3u8")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -1,174 +0,0 @@
|
|||
package com.lagradost.cloudstream3.animeproviders
|
||||
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||
import org.jsoup.Jsoup
|
||||
import java.util.*
|
||||
|
||||
class KawaiifuProvider : MainAPI() {
|
||||
override var mainUrl = "https://kawaiifu.com"
|
||||
override var name = "Kawaiifu"
|
||||
override val hasQuickSearch = false
|
||||
override val hasMainPage = true
|
||||
|
||||
override val supportedTypes = setOf(TvType.Anime, TvType.AnimeMovie)
|
||||
|
||||
override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse {
|
||||
val items = ArrayList<HomePageList>()
|
||||
val resp = app.get(mainUrl).text
|
||||
|
||||
val soup = Jsoup.parse(resp)
|
||||
|
||||
items.add(HomePageList("Latest Updates", soup.select(".today-update .item").mapNotNull {
|
||||
val title = it.selectFirst("img")?.attr("alt")
|
||||
AnimeSearchResponse(
|
||||
title ?: return@mapNotNull null,
|
||||
it.selectFirst("a")?.attr("href") ?: return@mapNotNull null,
|
||||
this.name,
|
||||
TvType.Anime,
|
||||
it.selectFirst("img")?.attr("src"),
|
||||
it.selectFirst("h4 > a")?.attr("href")?.split("-")?.last()?.toIntOrNull(),
|
||||
if (title.contains("(DUB)")) EnumSet.of(DubStatus.Dubbed) else EnumSet.of(
|
||||
DubStatus.Subbed
|
||||
),
|
||||
)
|
||||
}))
|
||||
for (section in soup.select(".section")) {
|
||||
try {
|
||||
val title = section.selectFirst(".title")!!.text()
|
||||
val anime = section.select(".list-film > .item").mapNotNull { ani ->
|
||||
val animTitle = ani.selectFirst("img")?.attr("alt")
|
||||
AnimeSearchResponse(
|
||||
animTitle ?: return@mapNotNull null,
|
||||
ani.selectFirst("a")?.attr("href") ?: return@mapNotNull null,
|
||||
this.name,
|
||||
TvType.Anime,
|
||||
ani.selectFirst("img")?.attr("src"),
|
||||
ani.selectFirst(".vl-chil-date")?.text()?.toIntOrNull(),
|
||||
if (animTitle.contains("(DUB)")) EnumSet.of(DubStatus.Dubbed) else EnumSet.of(
|
||||
DubStatus.Subbed
|
||||
),
|
||||
)
|
||||
}
|
||||
items.add(HomePageList(title, anime))
|
||||
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
if (items.size <= 0) throw ErrorLoadingException()
|
||||
return HomePageResponse(items)
|
||||
}
|
||||
|
||||
|
||||
override suspend fun search(query: String): ArrayList<SearchResponse> {
|
||||
val link = "$mainUrl/search-movie?keyword=${query}"
|
||||
val html = app.get(link).text
|
||||
val soup = Jsoup.parse(html)
|
||||
|
||||
return ArrayList(soup.select(".item").mapNotNull {
|
||||
val year = it.selectFirst("h4 > a")?.attr("href")?.split("-")?.last()?.toIntOrNull()
|
||||
val title = it.selectFirst("img")?.attr("alt") ?: return@mapNotNull null
|
||||
val poster = it.selectFirst("img")?.attr("src")
|
||||
val uri = it.selectFirst("a")?.attr("href") ?: return@mapNotNull null
|
||||
AnimeSearchResponse(
|
||||
title,
|
||||
uri,
|
||||
this.name,
|
||||
TvType.Anime,
|
||||
poster,
|
||||
year,
|
||||
if (title.contains("(DUB)")) EnumSet.of(DubStatus.Dubbed) else EnumSet.of(DubStatus.Subbed),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val html = app.get(url).text
|
||||
val soup = Jsoup.parse(html)
|
||||
|
||||
val title = soup.selectFirst(".title")!!.text()
|
||||
val tags = soup.select(".table a[href*=\"/tag/\"]").map { tag -> tag.text() }
|
||||
val description = soup.select(".sub-desc p")
|
||||
.filter { it -> it.select("strong").isEmpty() && it.select("iframe").isEmpty() }
|
||||
.joinToString("\n") { it.text() }
|
||||
val year = url.split("/").filter { it.contains("-") }[0].split("-")[1].toIntOrNull()
|
||||
|
||||
val episodesLink = soup.selectFirst("a[href*=\".html-episode\"]")?.attr("href")
|
||||
?: throw ErrorLoadingException("Error getting episode list")
|
||||
val episodes = Jsoup.parse(
|
||||
app.get(episodesLink).text
|
||||
).selectFirst(".list-ep")?.select("li")?.map {
|
||||
Episode(
|
||||
it.selectFirst("a")!!.attr("href"),
|
||||
if (it.text().trim().toIntOrNull() != null) "Episode ${
|
||||
it.text().trim()
|
||||
}" else it.text().trim()
|
||||
)
|
||||
}
|
||||
val poster = soup.selectFirst("a.thumb > img")?.attr("src")
|
||||
|
||||
return newAnimeLoadResponse(title, url, TvType.Anime) {
|
||||
this.year = year
|
||||
posterUrl = poster
|
||||
addEpisodes(DubStatus.Subbed, episodes)
|
||||
plot = description
|
||||
this.tags = tags
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
val htmlSource = app.get(data).text
|
||||
val soupa = Jsoup.parse(htmlSource)
|
||||
|
||||
val episodeNum =
|
||||
if (data.contains("ep=")) data.split("ep=")[1].split("&")[0].toIntOrNull() else null
|
||||
|
||||
val servers = soupa.select(".list-server").map {
|
||||
val serverName = it.selectFirst(".server-name")!!.text()
|
||||
val episodes = it.select(".list-ep > li > a")
|
||||
.map { episode -> Pair(episode.attr("href"), episode.text()) }
|
||||
val episode = if (episodeNum == null) episodes[0] else episodes.mapNotNull { ep ->
|
||||
if ((if (ep.first.contains("ep=")) ep.first.split("ep=")[1].split("&")[0].toIntOrNull() else null) == episodeNum) {
|
||||
ep
|
||||
} else null
|
||||
}[0]
|
||||
Pair(serverName, episode)
|
||||
}.map {
|
||||
if (it.second.first == data) {
|
||||
val sources = soupa.select("video > source")
|
||||
.map { source -> Pair(source.attr("src"), source.attr("data-quality")) }
|
||||
Triple(it.first, sources, it.second.second)
|
||||
} else {
|
||||
val html = app.get(it.second.first).text
|
||||
val soup = Jsoup.parse(html)
|
||||
|
||||
val sources = soup.select("video > source")
|
||||
.map { source -> Pair(source.attr("src"), source.attr("data-quality")) }
|
||||
Triple(it.first, sources, it.second.second)
|
||||
}
|
||||
}
|
||||
|
||||
servers.forEach {
|
||||
it.second.forEach { source ->
|
||||
callback(
|
||||
ExtractorLink(
|
||||
"Kawaiifu",
|
||||
it.first,
|
||||
source.first,
|
||||
"",
|
||||
getQualityFromName(source.second),
|
||||
source.first.contains(".m3u")
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -1,152 +0,0 @@
|
|||
package com.lagradost.cloudstream3.animeproviders
|
||||
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
|
||||
class KimCartoonProvider : MainAPI() {
|
||||
|
||||
override var mainUrl = "https://kimcartoon.li"
|
||||
override var name = "Kim Cartoon"
|
||||
override val hasQuickSearch = true
|
||||
override val hasMainPage = true
|
||||
|
||||
override val supportedTypes = setOf(TvType.Cartoon)
|
||||
|
||||
private fun fixUrl(url: String): String {
|
||||
return if (url.startsWith("/")) mainUrl + url else url
|
||||
}
|
||||
|
||||
override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse {
|
||||
val doc = app.get(mainUrl).document.select("#container")
|
||||
val response = mutableListOf(
|
||||
HomePageList(
|
||||
"Latest Update",
|
||||
doc.select("div.bigBarContainer div.items > div > a").map {
|
||||
AnimeSearchResponse(
|
||||
it.select(".item-title").let { div ->
|
||||
//Because it doesn't contain Title separately
|
||||
div.text().replace(div.select("span").text(), "")
|
||||
},
|
||||
mainUrl + it.attr("href"),
|
||||
mainUrl,
|
||||
TvType.Cartoon,
|
||||
fixUrl(it.select("img").let { img ->
|
||||
img.attr("src").let { src ->
|
||||
src.ifEmpty { img.attr("srctemp") }
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
val list = mapOf(
|
||||
"Top Day" to "tab-top-day",
|
||||
"Top Week" to "tab-top-week",
|
||||
"Top Month" to "tab-top-month",
|
||||
"New Cartoons" to "tab-newest-series"
|
||||
)
|
||||
response.addAll(list.map { item ->
|
||||
HomePageList(
|
||||
item.key,
|
||||
doc.select("#${item.value} > div").map {
|
||||
AnimeSearchResponse(
|
||||
it.select("span.title").text(),
|
||||
mainUrl + it.select("a")[0].attr("href"),
|
||||
mainUrl,
|
||||
TvType.Cartoon,
|
||||
fixUrl(it.select("a > img").attr("src"))
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
return HomePageResponse(response)
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
return app.post(
|
||||
"$mainUrl/Search/Cartoon",
|
||||
data = mapOf("keyword" to query)
|
||||
).document
|
||||
.select("#leftside > div.bigBarContainer div.list-cartoon > div.item > a")
|
||||
.map {
|
||||
AnimeSearchResponse(
|
||||
it.select("span").text(),
|
||||
mainUrl + it.attr("href"),
|
||||
mainUrl,
|
||||
TvType.Cartoon,
|
||||
fixUrl(it.select("img").attr("src"))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun quickSearch(query: String): List<SearchResponse> {
|
||||
return app.post(
|
||||
"$mainUrl/Ajax/SearchSuggest",
|
||||
data = mapOf("keyword" to query)
|
||||
).document.select("a").map {
|
||||
AnimeSearchResponse(
|
||||
it.text(),
|
||||
it.attr("href"),
|
||||
mainUrl,
|
||||
TvType.Cartoon,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun getStatus(from: String?): ShowStatus? {
|
||||
return when {
|
||||
from?.contains("Completed") == true -> ShowStatus.Completed
|
||||
from?.contains("Ongoing") == true -> ShowStatus.Ongoing
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val doc = app.get(url).document.select("#leftside")
|
||||
val info = doc.select("div.barContent")
|
||||
val name = info.select("a.bigChar").text()
|
||||
val eps = doc.select("table.listing > tbody > tr a").reversed().map {
|
||||
Episode(
|
||||
fixUrl(it.attr("href")),
|
||||
it.text().replace(name, "").trim()
|
||||
)
|
||||
}
|
||||
val infoText = info.text()
|
||||
fun getData(after: String, before: String): String? {
|
||||
return if (infoText.contains(after))
|
||||
infoText
|
||||
.substringAfter("$after:")
|
||||
.substringBefore(before)
|
||||
.trim()
|
||||
else null
|
||||
}
|
||||
|
||||
return newTvSeriesLoadResponse(name, url, TvType.Cartoon, eps) {
|
||||
posterUrl = fixUrl(info.select("div > img").attr("src"))
|
||||
showStatus = getStatus(getData("Status", "Views"))
|
||||
plot = getData("Summary", "Tags:")
|
||||
tags = getData("Genres", "Date aired")?.split(",")
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
val servers =
|
||||
app.get(data).document.select("#selectServer > option").map { fixUrl(it.attr("value")) }
|
||||
servers.apmap {
|
||||
app.get(it).document.select("#my_video_1").attr("src").let { iframe ->
|
||||
if (iframe.isNotEmpty()) {
|
||||
loadExtractor(iframe, "$mainUrl/", subtitleCallback, callback)
|
||||
}
|
||||
//There are other servers, but they require some work to do
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -1,176 +0,0 @@
|
|||
package com.lagradost.cloudstream3.animeproviders
|
||||
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Element
|
||||
|
||||
class KuramanimeProvider : MainAPI() {
|
||||
override var mainUrl = "https://kuramanime.com"
|
||||
override var name = "Kuramanime"
|
||||
override val hasQuickSearch = false
|
||||
override val hasMainPage = true
|
||||
override var lang = "id"
|
||||
override val hasDownloadSupport = true
|
||||
|
||||
override val supportedTypes = setOf(
|
||||
TvType.Anime,
|
||||
TvType.AnimeMovie,
|
||||
TvType.OVA
|
||||
)
|
||||
|
||||
companion object {
|
||||
fun getType(t: String): TvType {
|
||||
return if (t.contains("OVA") || t.contains("Special")) TvType.OVA
|
||||
else if (t.contains("Movie")) TvType.AnimeMovie
|
||||
else TvType.Anime
|
||||
}
|
||||
|
||||
fun getStatus(t: String): ShowStatus {
|
||||
return when (t) {
|
||||
"Selesai Tayang" -> ShowStatus.Completed
|
||||
"Sedang Tayang" -> ShowStatus.Ongoing
|
||||
else -> ShowStatus.Completed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val mainPage = mainPageOf(
|
||||
"$mainUrl/anime/ongoing?order_by=updated&page=" to "Sedang Tayang",
|
||||
"$mainUrl/anime/finished?order_by=updated&page=" to "Selesai Tayang",
|
||||
"$mainUrl/properties/season/summer-2022?order_by=most_viewed&page=" to "Dilihat Terbanyak Musim Ini",
|
||||
"$mainUrl/anime/movie?order_by=updated&page=" to "Film Layar Lebar",
|
||||
)
|
||||
|
||||
override suspend fun getMainPage(
|
||||
page: Int,
|
||||
request: MainPageRequest
|
||||
): HomePageResponse {
|
||||
val document = app.get(request.data + page).document
|
||||
|
||||
val home = document.select("div.col-lg-4.col-md-6.col-sm-6").mapNotNull {
|
||||
it.toSearchResult()
|
||||
}
|
||||
|
||||
return newHomePageResponse(request.name, home)
|
||||
}
|
||||
|
||||
private fun getProperAnimeLink(uri: String): String {
|
||||
return if (uri.contains("/episode")) {
|
||||
Regex("(.*)/episode/.+").find(uri)?.groupValues?.get(1).toString() + "/"
|
||||
} else {
|
||||
uri
|
||||
}
|
||||
}
|
||||
|
||||
private fun Element.toSearchResult(): AnimeSearchResponse? {
|
||||
val href = getProperAnimeLink(fixUrl(this.selectFirst("a")!!.attr("href")))
|
||||
val title = this.selectFirst("h5 a")?.text() ?: return null
|
||||
val posterUrl = fixUrl(this.select("div.product__item__pic.set-bg").attr("data-setbg"))
|
||||
val episode = Regex("([0-9*])\\s?/").find(
|
||||
this.select("div.ep span").text()
|
||||
)?.groupValues?.getOrNull(1)?.toIntOrNull()
|
||||
|
||||
return newAnimeSearchResponse(title, href, TvType.Anime) {
|
||||
this.posterUrl = posterUrl
|
||||
addSub(episode)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
val link = "$mainUrl/anime?search=$query&order_by=oldest"
|
||||
val document = app.get(link).document
|
||||
|
||||
return document.select(".product__item").mapNotNull {
|
||||
val title = it.selectFirst("div.product__item__text > h5")!!.text().trim()
|
||||
val poster = it.selectFirst("a > div")!!.attr("data-setbg")
|
||||
val tvType =
|
||||
getType(it.selectFirst(".product__item__text > ul > li")!!.text().toString())
|
||||
val href = fixUrl(it.selectFirst("a")!!.attr("href"))
|
||||
|
||||
newAnimeSearchResponse(title, href, tvType) {
|
||||
this.posterUrl = poster
|
||||
addDubStatus(dubExist = false, subExist = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val document = app.get(url).document
|
||||
|
||||
val title = document.selectFirst(".anime__details__title > h3")!!.text().trim()
|
||||
val poster = document.selectFirst(".anime__details__pic")?.attr("data-setbg")
|
||||
val tags =
|
||||
document.select("div.anime__details__widget > div > div:nth-child(2) > ul > li:nth-child(1)")
|
||||
.text().trim().replace("Genre: ", "").split(", ")
|
||||
|
||||
val year = Regex("[^0-9]").replace(
|
||||
document.select("div.anime__details__widget > div > div:nth-child(1) > ul > li:nth-child(5)")
|
||||
.text().trim().replace("Musim: ", ""), ""
|
||||
).toIntOrNull()
|
||||
val status = getStatus(
|
||||
document.select("div.anime__details__widget > div > div:nth-child(1) > ul > li:nth-child(3)")
|
||||
.text().trim().replace("Status: ", "")
|
||||
)
|
||||
val description = document.select(".anime__details__text > p").text().trim()
|
||||
|
||||
val episodes =
|
||||
Jsoup.parse(document.select("#episodeLists").attr("data-content")).select("a").map {
|
||||
val name = it.text().trim()
|
||||
val link = it.attr("href")
|
||||
Episode(link, name)
|
||||
}
|
||||
|
||||
val recommendations = document.select("div#randomList > a").mapNotNull {
|
||||
val epHref = it.attr("href")
|
||||
val epTitle = it.select("h5.sidebar-title-h5.px-2.py-2").text()
|
||||
val epPoster = it.select(".product__sidebar__view__item.set-bg").attr("data-setbg")
|
||||
|
||||
newAnimeSearchResponse(epTitle, epHref, TvType.Anime) {
|
||||
this.posterUrl = epPoster
|
||||
addDubStatus(dubExist = false, subExist = true)
|
||||
}
|
||||
}
|
||||
|
||||
return newAnimeLoadResponse(title, url, TvType.Anime) {
|
||||
engName = title
|
||||
posterUrl = poster
|
||||
this.year = year
|
||||
addEpisodes(DubStatus.Subbed, episodes)
|
||||
showStatus = status
|
||||
plot = description
|
||||
this.tags = tags
|
||||
this.recommendations = recommendations
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
val servers = app.get(data).document
|
||||
servers.select("video#player > source").map {
|
||||
suspendSafeApiCall {
|
||||
val url = it.attr("src")
|
||||
val quality = it.attr("size").toInt()
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
name,
|
||||
name,
|
||||
url,
|
||||
referer = "$mainUrl/",
|
||||
quality = quality
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
|
@ -1,197 +0,0 @@
|
|||
package com.lagradost.cloudstream3.animeproviders
|
||||
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Element
|
||||
|
||||
class KuronimeProvider : MainAPI() {
|
||||
override var mainUrl = "https://45.12.2.2"
|
||||
override var name = "Kuronime"
|
||||
override val hasQuickSearch = false
|
||||
override val hasMainPage = true
|
||||
override var lang = "id"
|
||||
override val hasDownloadSupport = true
|
||||
|
||||
override val supportedTypes = setOf(
|
||||
TvType.Anime,
|
||||
TvType.AnimeMovie,
|
||||
TvType.OVA
|
||||
)
|
||||
|
||||
companion object {
|
||||
fun getType(t: String): TvType {
|
||||
return if (t.contains("OVA") || t.contains("Special")) TvType.OVA
|
||||
else if (t.contains("Movie")) TvType.AnimeMovie
|
||||
else TvType.Anime
|
||||
}
|
||||
|
||||
fun getStatus(t: String): ShowStatus {
|
||||
return when (t) {
|
||||
"Completed" -> ShowStatus.Completed
|
||||
"Ongoing" -> ShowStatus.Ongoing
|
||||
else -> ShowStatus.Completed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val mainPage = mainPageOf(
|
||||
"$mainUrl/page/" to "New Episodes",
|
||||
"$mainUrl/popular-anime/page/" to "Popular Anime",
|
||||
"$mainUrl/movies/page/" to "Movies",
|
||||
"$mainUrl/genres/donghua/page/" to "Donghua",
|
||||
"$mainUrl/live-action/page/" to "Live Action",
|
||||
)
|
||||
|
||||
override suspend fun getMainPage(
|
||||
page: Int,
|
||||
request: MainPageRequest
|
||||
): HomePageResponse {
|
||||
val document = app.get(request.data + page).document
|
||||
val home = document.select("article").map {
|
||||
it.toSearchResult()
|
||||
}
|
||||
return newHomePageResponse(request.name, home)
|
||||
}
|
||||
|
||||
private fun getProperAnimeLink(uri: String): String {
|
||||
return if (uri.contains("/anime/")) {
|
||||
uri
|
||||
} else {
|
||||
var title = uri.substringAfter("$mainUrl/")
|
||||
title = when {
|
||||
(title.contains("-episode")) && !(title.contains("-movie")) -> Regex("nonton-(.+)-episode").find(
|
||||
title
|
||||
)?.groupValues?.get(1).toString()
|
||||
(title.contains("-movie")) -> Regex("nonton-(.+)-movie").find(title)?.groupValues?.get(
|
||||
1
|
||||
).toString()
|
||||
else -> title
|
||||
}
|
||||
|
||||
"$mainUrl/anime/$title"
|
||||
}
|
||||
}
|
||||
|
||||
private fun Element.toSearchResult(): AnimeSearchResponse {
|
||||
val href = getProperAnimeLink(fixUrlNull(this.selectFirst("a")?.attr("href")).toString())
|
||||
val title = this.select(".bsuxtt, .tt > h4").text().trim()
|
||||
val posterUrl = fixUrlNull(
|
||||
this.selectFirst("div.view,div.bt")?.nextElementSibling()?.select("img")
|
||||
?.attr("data-src")
|
||||
)
|
||||
val epNum = this.select(".ep").text().replace(Regex("[^0-9]"), "").trim().toIntOrNull()
|
||||
val tvType = getType(this.selectFirst(".bt > span")?.text().toString())
|
||||
return newAnimeSearchResponse(title, href, tvType) {
|
||||
this.posterUrl = posterUrl
|
||||
addSub(epNum)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
val link = "$mainUrl/?s=$query"
|
||||
val document = app.get(link).document
|
||||
|
||||
return document.select("article.bs").map {
|
||||
it.toSearchResult()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val document = app.get(url).document
|
||||
|
||||
val title = document.selectFirst(".entry-title")?.text().toString().trim()
|
||||
val poster = document.selectFirst("div.l[itemprop=image] > img")?.attr("data-src")
|
||||
val tags = document.select(".infodetail > ul > li:nth-child(2) > a").map { it.text() }
|
||||
val type = getType(
|
||||
document.selectFirst(".infodetail > ul > li:nth-child(7)")?.ownText()?.trim().toString()
|
||||
)
|
||||
val trailer = document.selectFirst("div.tply iframe")?.attr("data-lazy-src")
|
||||
val year = Regex("\\d, ([0-9]*)").find(
|
||||
document.select(".infodetail > ul > li:nth-child(5)").text()
|
||||
)?.groupValues?.get(1)?.toIntOrNull()
|
||||
val status = getStatus(
|
||||
document.selectFirst(".infodetail > ul > li:nth-child(3)")!!.ownText()
|
||||
.replace(Regex("\\W"), "")
|
||||
)
|
||||
val description = document.select("span.const > p").text()
|
||||
|
||||
val episodes = document.select("div.bixbox.bxcl > ul > li").map {
|
||||
val name = it.selectFirst("a")?.text()?.trim()
|
||||
val episode =
|
||||
it.selectFirst("a")?.text()?.trim()?.replace("Episode", "")?.trim()?.toIntOrNull()
|
||||
val link = it.selectFirst("a")!!.attr("href")
|
||||
Episode(link, name = name, episode = episode)
|
||||
}.reversed()
|
||||
|
||||
return newAnimeLoadResponse(title, url, type) {
|
||||
engName = title
|
||||
posterUrl = poster
|
||||
this.year = year
|
||||
addEpisodes(DubStatus.Subbed, episodes)
|
||||
showStatus = status
|
||||
plot = description
|
||||
addTrailer(trailer)
|
||||
this.tags = tags
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun invokeKuroSource(
|
||||
url: String,
|
||||
sourceCallback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
val doc = app.get(url, referer = "${mainUrl}/").document
|
||||
|
||||
doc.select("script").map { script ->
|
||||
if (script.data().contains("function jalankan_jwp() {")) {
|
||||
val data = script.data()
|
||||
val doma = data.substringAfter("var doma = \"").substringBefore("\";")
|
||||
val token = data.substringAfter("var token = \"").substringBefore("\";")
|
||||
val pat = data.substringAfter("var pat = \"").substringBefore("\";")
|
||||
val link = "$doma$token$pat/index.m3u8"
|
||||
val quality =
|
||||
Regex("\\d{3,4}p").find(doc.select("title").text())?.groupValues?.get(0)
|
||||
|
||||
sourceCallback.invoke(
|
||||
ExtractorLink(
|
||||
this.name,
|
||||
this.name,
|
||||
link,
|
||||
referer = "https://animeku.org/",
|
||||
quality = getQualityFromName(quality),
|
||||
headers = mapOf("Origin" to "https://animeku.org"),
|
||||
isM3u8 = true
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
val document = app.get(data).document
|
||||
val sources = document.select(".mobius > .mirror > option").mapNotNull {
|
||||
fixUrl(Jsoup.parse(base64Decode(it.attr("value"))).select("iframe").attr("data-src"))
|
||||
}
|
||||
|
||||
sources.apmap {
|
||||
safeApiCall {
|
||||
when {
|
||||
it.startsWith("https://animeku.org") -> invokeKuroSource(it, callback)
|
||||
else -> loadExtractor(it, mainUrl, subtitleCallback, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
|
@ -1,156 +0,0 @@
|
|||
package com.lagradost.cloudstream3.animeproviders
|
||||
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.extractors.FEmbed
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
import java.util.*
|
||||
|
||||
|
||||
class MonoschinosProvider : MainAPI() {
|
||||
companion object {
|
||||
fun getType(t: String): TvType {
|
||||
return if (t.contains("OVA") || t.contains("Especial")) TvType.OVA
|
||||
else if (t.contains("Pelicula")) TvType.AnimeMovie
|
||||
else TvType.Anime
|
||||
}
|
||||
|
||||
fun getDubStatus(title: String): DubStatus {
|
||||
return if (title.contains("Latino") || title.contains("Castellano"))
|
||||
DubStatus.Dubbed
|
||||
else DubStatus.Subbed
|
||||
}
|
||||
}
|
||||
|
||||
override var mainUrl = "https://monoschinos2.com"
|
||||
override var name = "Monoschinos"
|
||||
override var lang = "es"
|
||||
override val hasMainPage = true
|
||||
override val hasChromecastSupport = true
|
||||
override val hasDownloadSupport = true
|
||||
override val supportedTypes = setOf(
|
||||
TvType.AnimeMovie,
|
||||
TvType.OVA,
|
||||
TvType.Anime,
|
||||
)
|
||||
|
||||
override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse {
|
||||
val urls = listOf(
|
||||
Pair("$mainUrl/emision", "En emisión"),
|
||||
Pair(
|
||||
"$mainUrl/animes?categoria=pelicula&genero=false&fecha=false&letra=false",
|
||||
"Peliculas"
|
||||
),
|
||||
Pair("$mainUrl/animes", "Animes"),
|
||||
)
|
||||
|
||||
val items = ArrayList<HomePageList>()
|
||||
|
||||
items.add(
|
||||
HomePageList(
|
||||
"Capítulos actualizados",
|
||||
app.get(mainUrl, timeout = 120).document.select(".col-6").map {
|
||||
val title = it.selectFirst("p.animetitles")?.text() ?: it.selectFirst(".animetitles")?.text() ?: ""
|
||||
val poster = it.selectFirst(".animeimghv")!!.attr("data-src")
|
||||
val epRegex = Regex("episodio-(\\d+)")
|
||||
val url = it.selectFirst("a")?.attr("href")!!.replace("ver/", "anime/")
|
||||
.replace(epRegex, "sub-espanol")
|
||||
val epNum = (it.selectFirst(".positioning h5")?.text() ?: it.selectFirst("div.positioning p")?.text())?.toIntOrNull()
|
||||
newAnimeSearchResponse(title, url) {
|
||||
this.posterUrl = fixUrl(poster)
|
||||
addDubStatus(getDubStatus(title), epNum)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
for (i in urls) {
|
||||
try {
|
||||
val home = app.get(i.first, timeout = 120).document.select(".col-6").map {
|
||||
val title = it.selectFirst(".seristitles")!!.text()
|
||||
val poster = it.selectFirst("img.animemainimg")!!.attr("src")
|
||||
newAnimeSearchResponse(title, fixUrl(it.selectFirst("a")!!.attr("href"))) {
|
||||
this.posterUrl = fixUrl(poster)
|
||||
addDubStatus(getDubStatus(title))
|
||||
}
|
||||
}
|
||||
|
||||
items.add(HomePageList(i.second, home))
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
if (items.size <= 0) throw ErrorLoadingException()
|
||||
return HomePageResponse(items)
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): ArrayList<SearchResponse> {
|
||||
val search =
|
||||
app.get("$mainUrl/buscar?q=$query", timeout = 120).document.select(".col-6").map {
|
||||
val title = it.selectFirst(".seristitles")!!.text()
|
||||
val href = fixUrl(it.selectFirst("a")!!.attr("href"))
|
||||
val image = it.selectFirst("img.animemainimg")!!.attr("src")
|
||||
AnimeSearchResponse(
|
||||
title,
|
||||
href,
|
||||
this.name,
|
||||
TvType.Anime,
|
||||
fixUrl(image),
|
||||
null,
|
||||
if (title.contains("Latino") || title.contains("Castellano")) EnumSet.of(
|
||||
DubStatus.Dubbed
|
||||
) else EnumSet.of(DubStatus.Subbed),
|
||||
)
|
||||
}
|
||||
return ArrayList(search)
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val doc = app.get(url, timeout = 120).document
|
||||
val poster = doc.selectFirst(".chapterpic img")!!.attr("src")
|
||||
val title = doc.selectFirst(".chapterdetails h1")!!.text()
|
||||
val type = doc.selectFirst("div.chapterdetls2")!!.text()
|
||||
val description = doc.selectFirst("p.textComplete")!!.text().replace("Ver menos", "")
|
||||
val genres = doc.select(".breadcrumb-item a").map { it.text() }
|
||||
val status = when (doc.selectFirst("button.btn1")?.text()) {
|
||||
"Estreno" -> ShowStatus.Ongoing
|
||||
"Finalizado" -> ShowStatus.Completed
|
||||
else -> null
|
||||
}
|
||||
val episodes = doc.select("div.col-item").map {
|
||||
val name = it.selectFirst("p.animetitles")!!.text()
|
||||
val link = it.selectFirst("a")!!.attr("href")
|
||||
val epThumb = it.selectFirst(".animeimghv")!!.attr("data-src")
|
||||
Episode(link, name, posterUrl = epThumb)
|
||||
}
|
||||
return newAnimeLoadResponse(title, url, getType(type)) {
|
||||
posterUrl = poster
|
||||
addEpisodes(DubStatus.Subbed, episodes)
|
||||
showStatus = status
|
||||
plot = description
|
||||
tags = genres
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
app.get(data).document.select("div.playother p").forEach {
|
||||
val encodedurl = it.select("p").attr("data-player")
|
||||
val urlDecoded = base64Decode(encodedurl)
|
||||
val url = (urlDecoded).replace("https://monoschinos2.com/reproductor?url=", "")
|
||||
if (url.startsWith("https://www.fembed.com")) {
|
||||
val extractor = FEmbed()
|
||||
extractor.getUrl(url).forEach { link ->
|
||||
callback.invoke(link)
|
||||
}
|
||||
} else {
|
||||
loadExtractor(url, mainUrl, subtitleCallback, callback)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -1,217 +0,0 @@
|
|||
package com.lagradost.cloudstream3.animeproviders
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8
|
||||
import com.lagradost.cloudstream3.utils.getAndUnpack
|
||||
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
import java.util.*
|
||||
|
||||
|
||||
class MundoDonghuaProvider : MainAPI() {
|
||||
|
||||
override var mainUrl = "https://www.mundodonghua.com"
|
||||
override var name = "MundoDonghua"
|
||||
override var lang = "es"
|
||||
override val hasMainPage = true
|
||||
override val hasChromecastSupport = true
|
||||
override val hasDownloadSupport = true
|
||||
override val supportedTypes = setOf(
|
||||
TvType.Anime,
|
||||
)
|
||||
|
||||
override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse {
|
||||
val urls = listOf(
|
||||
Pair("$mainUrl/lista-donghuas", "Donghuas"),
|
||||
)
|
||||
|
||||
val items = ArrayList<HomePageList>()
|
||||
items.add(
|
||||
HomePageList(
|
||||
"Últimos episodios",
|
||||
app.get(mainUrl, timeout = 120).document.select("div.row .col-xs-4").map {
|
||||
val title = it.selectFirst("h5")?.text() ?: ""
|
||||
val poster = it.selectFirst(".fit-1 img")?.attr("src")
|
||||
val epRegex = Regex("(\\/(\\d+)\$)")
|
||||
val url = it.selectFirst("a")?.attr("href")?.replace(epRegex,"")?.replace("/ver/","/donghua/")
|
||||
val epnumRegex = Regex("((\\d+)$)")
|
||||
val epNum = epnumRegex.find(title)?.value?.toIntOrNull()
|
||||
val dubstat = if (title.contains("Latino") || title.contains("Castellano")) DubStatus.Dubbed else DubStatus.Subbed
|
||||
newAnimeSearchResponse(title.replace(Regex("Episodio|(\\d+)"),"").trim(), fixUrl(url ?: "")) {
|
||||
this.posterUrl = fixUrl(poster ?: "")
|
||||
addDubStatus(dubstat, epNum)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
urls.apmap { (url, name) ->
|
||||
val home = app.get(url, timeout = 120).document.select(".col-xs-4").map {
|
||||
val title = it.selectFirst(".fs-14")?.text() ?: ""
|
||||
val poster = it.selectFirst(".fit-1 img")?.attr("src") ?: ""
|
||||
AnimeSearchResponse(
|
||||
title,
|
||||
fixUrl(it.selectFirst("a")?.attr("href") ?: ""),
|
||||
this.name,
|
||||
TvType.Anime,
|
||||
fixUrl(poster),
|
||||
null,
|
||||
if (title.contains("Latino") || title.contains("Castellano")) EnumSet.of(
|
||||
DubStatus.Dubbed
|
||||
) else EnumSet.of(DubStatus.Subbed),
|
||||
)
|
||||
}
|
||||
|
||||
items.add(HomePageList(name, home))
|
||||
}
|
||||
|
||||
if (items.size <= 0) throw ErrorLoadingException()
|
||||
return HomePageResponse(items)
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
return app.get("$mainUrl/busquedas/$query", timeout = 120).document.select(".col-xs-4").map {
|
||||
val title = it.selectFirst(".fs-14")?.text() ?: ""
|
||||
val href = fixUrl(it.selectFirst("a")?.attr("href") ?: "")
|
||||
val image = it.selectFirst(".fit-1 img")?.attr("src")
|
||||
AnimeSearchResponse(
|
||||
title,
|
||||
href,
|
||||
this.name,
|
||||
TvType.Anime,
|
||||
fixUrl(image ?: ""),
|
||||
null,
|
||||
if (title.contains("Latino") || title.contains("Castellano")) EnumSet.of(
|
||||
DubStatus.Dubbed
|
||||
) else EnumSet.of(DubStatus.Subbed),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val doc = app.get(url, timeout = 120).document
|
||||
val poster = doc.selectFirst("head meta[property=og:image]")?.attr("content") ?: ""
|
||||
val title = doc.selectFirst(".ls-title-serie")?.text() ?: ""
|
||||
val description = doc.selectFirst("p.text-justify.fc-dark")?.text() ?: ""
|
||||
val genres = doc.select("span.label.label-primary.f-bold").map { it.text() }
|
||||
val status = when (doc.selectFirst("div.col-md-6.col-xs-6.align-center.bg-white.pt-10.pr-15.pb-0.pl-15 p span.badge.bg-default")?.text()) {
|
||||
"En Emisión" -> ShowStatus.Ongoing
|
||||
"Finalizada" -> ShowStatus.Completed
|
||||
else -> null
|
||||
}
|
||||
val episodes = doc.select("ul.donghua-list a").map {
|
||||
val name = it.selectFirst(".fs-16")?.text()
|
||||
val link = it.attr("href")
|
||||
Episode(fixUrl(link), name)
|
||||
}.reversed()
|
||||
val typeinfo = doc.select("div.row div.col-md-6.pl-15 p.fc-dark").text()
|
||||
val tvType = if (typeinfo.contains(Regex("Tipo.*Pel.cula"))) TvType.AnimeMovie else TvType.Anime
|
||||
return newAnimeLoadResponse(title, url, tvType) {
|
||||
posterUrl = poster
|
||||
addEpisodes(DubStatus.Subbed, episodes)
|
||||
showStatus = status
|
||||
plot = description
|
||||
tags = genres
|
||||
}
|
||||
}
|
||||
data class Protea (
|
||||
@JsonProperty("source") val source: List<Source>,
|
||||
@JsonProperty("poster") val poster: String?
|
||||
)
|
||||
|
||||
data class Source (
|
||||
@JsonProperty("file") val file: String,
|
||||
@JsonProperty("label") val label: String?,
|
||||
@JsonProperty("type") val type: String?,
|
||||
@JsonProperty("default") val default: String?
|
||||
)
|
||||
|
||||
private fun cleanStream(
|
||||
name: String,
|
||||
url: String,
|
||||
qualityString: String?,
|
||||
callback: (ExtractorLink) -> Unit,
|
||||
isM3U8: Boolean
|
||||
): Boolean {
|
||||
callback(
|
||||
ExtractorLink(
|
||||
name,
|
||||
name,
|
||||
url,
|
||||
"",
|
||||
getQualityFromName(qualityString),
|
||||
isM3U8
|
||||
)
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
app.get(data).document.select("script").apmap { script ->
|
||||
if (script.data().contains("eval(function(p,a,c,k,e")) {
|
||||
val packedRegex = Regex("eval\\(function\\(p,a,c,k,e,.*\\)\\)")
|
||||
packedRegex.findAll(script.data()).map {
|
||||
it.value
|
||||
}.toList().apmap {
|
||||
val unpack = getAndUnpack(it).replace("diasfem","embedsito")
|
||||
fetchUrls(unpack).apmap { url ->
|
||||
loadExtractor(url, data, subtitleCallback, callback)
|
||||
}
|
||||
if (unpack.contains("protea_tab")) {
|
||||
val protearegex = Regex("(protea_tab.*slug.*,type)")
|
||||
val slug = protearegex.findAll(unpack).map {
|
||||
it.value.replace(Regex("(protea_tab.*slug\":\")"),"").replace("\"},type","")
|
||||
}.first()
|
||||
val requestlink = "$mainUrl/api_donghua.php?slug=$slug"
|
||||
val response = app.get(requestlink, headers =
|
||||
mapOf("Host" to "www.mundodonghua.com",
|
||||
"User-Agent" to USER_AGENT,
|
||||
"Accept" to "*/*",
|
||||
"Accept-Language" to "en-US,en;q=0.5",
|
||||
"Referer" to data,
|
||||
"X-Requested-With" to "XMLHttpRequest",
|
||||
"DNT" to "1",
|
||||
"Connection" to "keep-alive",
|
||||
"Sec-Fetch-Dest" to "empty",
|
||||
"Sec-Fetch-Mode" to "no-cors",
|
||||
"Sec-Fetch-Site" to "same-origin",
|
||||
"TE" to "trailers",
|
||||
"Pragma" to "no-cache",
|
||||
"Cache-Control" to "no-cache",)
|
||||
).text.removePrefix("[").removeSuffix("]")
|
||||
val json = parseJson<Protea>(response)
|
||||
json.source.forEach { source ->
|
||||
val protename = "Protea"
|
||||
cleanStream(protename, fixUrl(source.file), source.label, callback, false)
|
||||
}
|
||||
}
|
||||
if (unpack.contains("asura_player")) {
|
||||
val asuraRegex = Regex("(asura_player.*type)")
|
||||
asuraRegex.findAll(unpack).map {
|
||||
it.value
|
||||
}.toList().apmap { protea ->
|
||||
val asuraname = "Asura"
|
||||
val file = protea.substringAfter("{file:\"").substringBefore("\"")
|
||||
generateM3u8(
|
||||
asuraname,
|
||||
file,
|
||||
""
|
||||
).forEach {
|
||||
cleanStream(asuraname, it.url, it.quality.toString(), callback, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -1,178 +0,0 @@
|
|||
package com.lagradost.cloudstream3.animeproviders
|
||||
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
import org.jsoup.nodes.Element
|
||||
import java.util.*
|
||||
|
||||
class NeonimeProvider : MainAPI() {
|
||||
override var mainUrl = "https://neonime.watch"
|
||||
override var name = "Neonime"
|
||||
override val hasQuickSearch = false
|
||||
override val hasMainPage = true
|
||||
override var lang = "id"
|
||||
override val hasDownloadSupport = true
|
||||
|
||||
override val supportedTypes = setOf(
|
||||
TvType.Anime,
|
||||
TvType.AnimeMovie,
|
||||
TvType.OVA
|
||||
)
|
||||
|
||||
companion object {
|
||||
fun getType(t: String): TvType {
|
||||
return if (t.contains("OVA") || t.contains("Special")) TvType.OVA
|
||||
else if (t.contains("Movie")) TvType.AnimeMovie
|
||||
else TvType.Anime
|
||||
}
|
||||
|
||||
fun getStatus(t: String): ShowStatus {
|
||||
return when (t) {
|
||||
"Ended" -> ShowStatus.Completed
|
||||
"OnGoing" -> ShowStatus.Ongoing
|
||||
"Ongoing" -> ShowStatus.Ongoing
|
||||
"In Production" -> ShowStatus.Ongoing
|
||||
"Returning Series" -> ShowStatus.Ongoing
|
||||
else -> ShowStatus.Completed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val mainPage = mainPageOf(
|
||||
"$mainUrl/episode/page/" to "Episode Terbaru",
|
||||
"$mainUrl/tvshows/page/" to "Anime Terbaru",
|
||||
"$mainUrl/movies/page/" to "Movie",
|
||||
)
|
||||
|
||||
override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse {
|
||||
val document = app.get(request.data + page).document
|
||||
val home = document.select("tbody tr,div.item").mapNotNull {
|
||||
it.toSearchResult()
|
||||
}
|
||||
return newHomePageResponse(request.name, home)
|
||||
}
|
||||
|
||||
private fun getProperAnimeLink(uri: String): String {
|
||||
return when {
|
||||
uri.contains("/episode") -> {
|
||||
val title = uri.substringAfter("$mainUrl/episode/").let { tt ->
|
||||
val fixTitle = Regex("(.*)-\\d{1,2}x\\d+").find(tt)?.groupValues?.getOrNull(1).toString()
|
||||
when {
|
||||
!tt.contains("-season") && !tt.contains(Regex("-1x\\d+")) && !tt.contains("one-piece") -> "$fixTitle-season-${Regex("-(\\d{1,2})x\\d+").find(tt)?.groupValues?.getOrNull(1).toString()}"
|
||||
tt.contains("-special") -> fixTitle.replace(Regex("-x\\d+"), "")
|
||||
!fixTitle.contains("-subtitle-indonesia") -> "$fixTitle-subtitle-indonesia"
|
||||
else -> fixTitle
|
||||
}
|
||||
}
|
||||
|
||||
// title = when {
|
||||
// title.contains("youkoso-jitsuryoku") && !title.contains("-season") -> title.replace("-e-", "-e-tv-")
|
||||
// else -> title
|
||||
// }
|
||||
|
||||
"$mainUrl/tvshows/$title"
|
||||
}
|
||||
else -> uri
|
||||
}
|
||||
}
|
||||
|
||||
private fun Element.toSearchResult(): AnimeSearchResponse? {
|
||||
val title = this.selectFirst("td.bb a")?.ownText() ?: this.selectFirst("h2")?.text() ?: return null
|
||||
val href = getProperAnimeLink(fixUrl(this.select("a").attr("href")))
|
||||
val posterUrl = fixUrl(this.select("img").attr("data-src"))
|
||||
val epNum = this.selectFirst("td.bb span")?.text()?.let { eps ->
|
||||
Regex("Episode\\s?([0-9]+)").find(eps)?.groupValues?.getOrNull(1)?.toIntOrNull()
|
||||
}
|
||||
|
||||
return newAnimeSearchResponse(title, href, TvType.Anime) {
|
||||
this.posterUrl = posterUrl
|
||||
addSub(epNum)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
val link = "$mainUrl/?s=$query"
|
||||
val document = app.get(link).document
|
||||
|
||||
return document.select("div.item.episode-home").mapNotNull {
|
||||
val title = it.selectFirst("div.judul-anime > span")!!.text()
|
||||
val poster = it.select("img").attr("data-src").toString().trim()
|
||||
val episodes = it.selectFirst("div.fixyear > h2.text-center")!!
|
||||
.text().replace(Regex("[^0-9]"), "").trim().toIntOrNull()
|
||||
val tvType = getType(it.selectFirst("span.calidad2.episode")?.text().toString())
|
||||
val href = getProperAnimeLink(fixUrl(it.selectFirst("a")!!.attr("href")))
|
||||
|
||||
newAnimeSearchResponse(title, href, tvType) {
|
||||
this.posterUrl = poster
|
||||
addSub(episodes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val document = app.get(url).document
|
||||
|
||||
if (url.contains("movie") || url.contains("live-action")) {
|
||||
val mTitle = document.selectFirst(".sbox > .data > h1[itemprop = name]")?.text().toString().trim()
|
||||
val mTrailer = document.selectFirst("div.youtube_id iframe")?.attr("data-wpfc-original-src")?.substringAfterLast("html#")?.let{ "https://www.youtube.com/embed/$it"}
|
||||
|
||||
return newMovieLoadResponse(name = mTitle, url = url, type = TvType.Movie, dataUrl = url) {
|
||||
posterUrl = document.selectFirst(".sbox > .imagen > .fix > img[itemprop = image]")?.attr("data-src")
|
||||
year = document.selectFirst("a[href*=release-year]")!!.text().toIntOrNull()
|
||||
plot = document.select("div[itemprop = description]").text().trim()
|
||||
rating = document.select("span[itemprop = ratingValue]").text().toIntOrNull()
|
||||
tags = document.select("p.meta_dd > a").map { it.text() }
|
||||
addTrailer(mTrailer)
|
||||
}
|
||||
}
|
||||
else {
|
||||
val title = document.select("h1[itemprop = name]").text().trim()
|
||||
val trailer = document.selectFirst("div.youtube_id_tv iframe")?.attr("data-wpfc-original-src")?.substringAfterLast("html#")?.let{ "https://www.youtube.com/embed/$it"}
|
||||
|
||||
val episodes = document.select("ul.episodios > li").mapNotNull {
|
||||
val header = it.selectFirst(".episodiotitle > a")?.ownText().toString()
|
||||
val name = Regex("(Episode\\s?[0-9]+)").find(header)?.groupValues?.getOrNull(0) ?: header
|
||||
val link = fixUrl(it.selectFirst(".episodiotitle > a")!!.attr("href"))
|
||||
Episode(link, name)
|
||||
}.reversed()
|
||||
|
||||
return newAnimeLoadResponse(title, url, TvType.Anime) {
|
||||
engName = title
|
||||
posterUrl = document.selectFirst(".imagen > img")?.attr("data-src")
|
||||
year = document.select("#info a[href*=\"-year/\"]").text().toIntOrNull()
|
||||
addEpisodes(DubStatus.Subbed, episodes)
|
||||
showStatus = getStatus(document.select("div.metadatac > span").last()!!.text().trim())
|
||||
plot = document.select("div[itemprop = description] > p").text().trim()
|
||||
tags = document.select("#info a[href*=\"-genre/\"]").map { it.text() }
|
||||
addTrailer(trailer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
val source = if(data.contains("movie") || data.contains("live-action")) {
|
||||
app.get(data).document.select("#player2-1 > div[id*=div]").mapNotNull {
|
||||
fixUrl(it.select("iframe").attr("data-src"))
|
||||
}
|
||||
} else {
|
||||
app.get(data).document.select(".player2 > .embed2 > div[id*=player]").mapNotNull {
|
||||
fixUrl(it.select("iframe").attr("data-src"))
|
||||
}
|
||||
}
|
||||
|
||||
source.apmap {
|
||||
loadExtractor(it, data, subtitleCallback, callback)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
|
@ -1,357 +0,0 @@
|
|||
package com.lagradost.cloudstream3.animeproviders
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
import org.jsoup.Jsoup
|
||||
|
||||
class NineAnimeProvider : MainAPI() {
|
||||
override var mainUrl = "https://9anime.id"
|
||||
override var name = "9Anime"
|
||||
override val hasMainPage = true
|
||||
override val hasChromecastSupport = true
|
||||
override val hasDownloadSupport = true
|
||||
override val supportedTypes = setOf(TvType.Anime)
|
||||
override val hasQuickSearch = true
|
||||
|
||||
// taken from https://github.com/saikou-app/saikou/blob/b35364c8c2a00364178a472fccf1ab72f09815b4/app/src/main/java/ani/saikou/parsers/anime/NineAnime.kt
|
||||
// GNU General Public License v3.0 https://github.com/saikou-app/saikou/blob/main/LICENSE.md
|
||||
companion object {
|
||||
private const val nineAnimeKey =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||
private const val cipherKey = "kMXzgyNzT3k5dYab"
|
||||
|
||||
fun encodeVrf(text: String, mainKey: String): String {
|
||||
return encode(
|
||||
encrypt(
|
||||
cipher(mainKey, encode(text)),
|
||||
nineAnimeKey
|
||||
)//.replace("""=+$""".toRegex(), "")
|
||||
)
|
||||
}
|
||||
|
||||
fun decodeVrf(text: String, mainKey: String): String {
|
||||
return decode(cipher(mainKey, decrypt(text, nineAnimeKey)))
|
||||
}
|
||||
|
||||
fun encrypt(input: String, key: String): String {
|
||||
if (input.any { it.code > 255 }) throw Exception("illegal characters!")
|
||||
var output = ""
|
||||
for (i in input.indices step 3) {
|
||||
val a = intArrayOf(-1, -1, -1, -1)
|
||||
a[0] = input[i].code shr 2
|
||||
a[1] = (3 and input[i].code) shl 4
|
||||
if (input.length > i + 1) {
|
||||
a[1] = a[1] or (input[i + 1].code shr 4)
|
||||
a[2] = (15 and input[i + 1].code) shl 2
|
||||
}
|
||||
if (input.length > i + 2) {
|
||||
a[2] = a[2] or (input[i + 2].code shr 6)
|
||||
a[3] = 63 and input[i + 2].code
|
||||
}
|
||||
for (n in a) {
|
||||
if (n == -1) output += "="
|
||||
else {
|
||||
if (n in 0..63) output += key[n]
|
||||
}
|
||||
}
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
fun cipher(key: String, text: String): String {
|
||||
val arr = IntArray(256) { it }
|
||||
|
||||
var u = 0
|
||||
var r: Int
|
||||
arr.indices.forEach {
|
||||
u = (u + arr[it] + key[it % key.length].code) % 256
|
||||
r = arr[it]
|
||||
arr[it] = arr[u]
|
||||
arr[u] = r
|
||||
}
|
||||
u = 0
|
||||
var c = 0
|
||||
|
||||
return text.indices.map { j ->
|
||||
c = (c + 1) % 256
|
||||
u = (u + arr[c]) % 256
|
||||
r = arr[c]
|
||||
arr[c] = arr[u]
|
||||
arr[u] = r
|
||||
(text[j].code xor arr[(arr[c] + arr[u]) % 256]).toChar()
|
||||
}.joinToString("")
|
||||
}
|
||||
|
||||
@Suppress("SameParameterValue")
|
||||
private fun decrypt(input: String, key: String): String {
|
||||
val t = if (input.replace("""[\t\n\f\r]""".toRegex(), "").length % 4 == 0) {
|
||||
input.replace("""==?$""".toRegex(), "")
|
||||
} else input
|
||||
if (t.length % 4 == 1 || t.contains("""[^+/0-9A-Za-z]""".toRegex())) throw Exception("bad input")
|
||||
var i: Int
|
||||
var r = ""
|
||||
var e = 0
|
||||
var u = 0
|
||||
for (o in t.indices) {
|
||||
e = e shl 6
|
||||
i = key.indexOf(t[o])
|
||||
e = e or i
|
||||
u += 6
|
||||
if (24 == u) {
|
||||
r += ((16711680 and e) shr 16).toChar()
|
||||
r += ((65280 and e) shr 8).toChar()
|
||||
r += (255 and e).toChar()
|
||||
e = 0
|
||||
u = 0
|
||||
}
|
||||
}
|
||||
return if (12 == u) {
|
||||
e = e shr 4
|
||||
r + e.toChar()
|
||||
} else {
|
||||
if (18 == u) {
|
||||
e = e shr 2
|
||||
r += ((65280 and e) shr 8).toChar()
|
||||
r += (255 and e).toChar()
|
||||
}
|
||||
r
|
||||
}
|
||||
}
|
||||
|
||||
fun encode(input: String): String =
|
||||
java.net.URLEncoder.encode(input, "utf-8").replace("+", "%20")
|
||||
|
||||
private fun decode(input: String): String = java.net.URLDecoder.decode(input, "utf-8")
|
||||
}
|
||||
|
||||
override val mainPage = mainPageOf(
|
||||
"$mainUrl/ajax/home/widget/trending?page=" to "Trending",
|
||||
"$mainUrl/ajax/home/widget/updated-all?page=" to "All",
|
||||
"$mainUrl/ajax/home/widget/updated-sub?page=" to "Recently Updated (SUB)",
|
||||
"$mainUrl/ajax/home/widget/updated-dub?page=" to "Recently Updated (DUB)",
|
||||
"$mainUrl/ajax/home/widget/updated-china?page=" to "Recently Updated (Chinese)",
|
||||
"$mainUrl/ajax/home/widget/random?page=" to "Random",
|
||||
)
|
||||
|
||||
override suspend fun getMainPage(
|
||||
page: Int,
|
||||
request: MainPageRequest
|
||||
): HomePageResponse {
|
||||
val url = request.data + page
|
||||
val home = Jsoup.parse(
|
||||
app.get(
|
||||
url
|
||||
).parsed<Response>().html
|
||||
).select("div.item").mapNotNull { element ->
|
||||
val title = element.selectFirst(".info > .name") ?: return@mapNotNull null
|
||||
val link = title.attr("href")
|
||||
val poster = element.selectFirst(".poster > a > img")?.attr("src")
|
||||
val meta = element.selectFirst(".poster > a > .meta > .inner > .left")
|
||||
val subbedEpisodes = meta?.selectFirst(".sub")?.text()?.toIntOrNull()
|
||||
val dubbedEpisodes = meta?.selectFirst(".dub")?.text()?.toIntOrNull()
|
||||
|
||||
newAnimeSearchResponse(title.text() ?: return@mapNotNull null, link) {
|
||||
this.posterUrl = poster
|
||||
addDubStatus(
|
||||
dubbedEpisodes != null,
|
||||
subbedEpisodes != null,
|
||||
dubbedEpisodes,
|
||||
subbedEpisodes
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return newHomePageResponse(request.name, home)
|
||||
}
|
||||
|
||||
data class Response(
|
||||
@JsonProperty("result") val html: String
|
||||
)
|
||||
|
||||
data class QuickSearchResponse(
|
||||
//@JsonProperty("status") val status: Int? = null,
|
||||
@JsonProperty("result") val result: QuickSearchResult? = null,
|
||||
//@JsonProperty("message") val message: String? = null,
|
||||
//@JsonProperty("messages") val messages: ArrayList<String> = arrayListOf()
|
||||
)
|
||||
|
||||
data class QuickSearchResult(
|
||||
@JsonProperty("html") val html: String? = null,
|
||||
//@JsonProperty("linkMore") val linkMore: String? = null
|
||||
)
|
||||
|
||||
override suspend fun quickSearch(query: String): List<SearchResponse>? {
|
||||
val vrf = encodeVrf(query, cipherKey)
|
||||
val url =
|
||||
"$mainUrl/ajax/anime/search?keyword=$query&vrf=$vrf"
|
||||
val response = app.get(url).parsedSafe<QuickSearchResponse>()
|
||||
val document = Jsoup.parse(response?.result?.html ?: return null)
|
||||
return document.select(".items > a").mapNotNull { element ->
|
||||
val link = fixUrl(element?.attr("href") ?: return@mapNotNull null)
|
||||
val title = element.selectFirst(".info > .name")?.text() ?: return@mapNotNull null
|
||||
newAnimeSearchResponse(title, link) {
|
||||
posterUrl = element.selectFirst(".poster > span > img")?.attr("src")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
val vrf = encodeVrf(query, cipherKey)
|
||||
//?language%5B%5D=${if (selectDub) "dubbed" else "subbed"}&
|
||||
val url =
|
||||
"$mainUrl/filter?keyword=${encode(query)}&vrf=${vrf}&page=1"
|
||||
return app.get(url).document.select("#list-items div.ani.poster.tip > a").mapNotNull {
|
||||
val link = fixUrl(it.attr("href") ?: return@mapNotNull null)
|
||||
val img = it.select("img")
|
||||
val title = img.attr("alt")
|
||||
newAnimeSearchResponse(title, link) {
|
||||
posterUrl = img.attr("src")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val validUrl = url.replace("https://9anime.to", mainUrl)
|
||||
val doc = app.get(validUrl).document
|
||||
|
||||
val meta = doc.selectFirst("#w-info") ?: throw ErrorLoadingException("Could not find info")
|
||||
val ratingElement = meta.selectFirst(".brating > #w-rating")
|
||||
val id = ratingElement?.attr("data-id") ?: throw ErrorLoadingException("Could not find id")
|
||||
val binfo =
|
||||
meta.selectFirst(".binfo") ?: throw ErrorLoadingException("Could not find binfo")
|
||||
val info = binfo.selectFirst(".info") ?: throw ErrorLoadingException("Could not find info")
|
||||
|
||||
val title = (info.selectFirst(".title") ?: info.selectFirst(".d-title"))?.text()
|
||||
?: throw ErrorLoadingException("Could not find title")
|
||||
|
||||
val vrf = encodeVrf(id, cipherKey)
|
||||
val episodeListUrl = "$mainUrl/ajax/episode/list/$id?vrf=$vrf"
|
||||
val body =
|
||||
app.get(episodeListUrl).parsedSafe<Response>()?.html
|
||||
?: throw ErrorLoadingException("Could not parse json with cipherKey=$cipherKey id=$id url=\n$episodeListUrl")
|
||||
|
||||
val subEpisodes = ArrayList<Episode>()
|
||||
val dubEpisodes = ArrayList<Episode>()
|
||||
|
||||
//TODO RECOMMENDATIONS
|
||||
|
||||
Jsoup.parse(body).body().select(".episodes > ul > li > a").mapNotNull { element ->
|
||||
val ids = element.attr("data-ids").split(",", limit = 2)
|
||||
|
||||
val epNum = element.attr("data-num")
|
||||
.toIntOrNull() // might fuck up on 7.5 ect might use data-slug instead
|
||||
val epTitle = element.selectFirst("span.d-title")?.text()
|
||||
//val filler = element.hasClass("filler")
|
||||
ids.getOrNull(1)?.let { dub ->
|
||||
dubEpisodes.add(
|
||||
Episode(
|
||||
"$mainUrl/ajax/server/list/$dub?vrf=${encodeVrf(dub, cipherKey)}",
|
||||
epTitle,
|
||||
episode = epNum
|
||||
)
|
||||
)
|
||||
}
|
||||
ids.getOrNull(0)?.let { sub ->
|
||||
subEpisodes.add(
|
||||
Episode(
|
||||
"$mainUrl/ajax/server/list/$sub?vrf=${encodeVrf(sub, cipherKey)}",
|
||||
epTitle,
|
||||
episode = epNum
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return newAnimeLoadResponse(title, url, TvType.Anime) {
|
||||
addEpisodes(DubStatus.Dubbed, dubEpisodes)
|
||||
addEpisodes(DubStatus.Subbed, subEpisodes)
|
||||
|
||||
plot = info.selectFirst(".synopsis > .shorting > .content")?.text()
|
||||
posterUrl = binfo.selectFirst(".poster > span > img")?.attr("src")
|
||||
rating = ratingElement.attr("data-score").toFloat().times(1000f).toInt()
|
||||
|
||||
info.select(".bmeta > .meta > div").forEach { element ->
|
||||
when (element.ownText()) {
|
||||
"Genre: " -> {
|
||||
tags = element.select("span > a").mapNotNull { it?.text() }
|
||||
}
|
||||
"Duration: " -> {
|
||||
duration = getDurationFromString(element.selectFirst("span")?.text())
|
||||
}
|
||||
"Type: " -> {
|
||||
type = when (element.selectFirst("span > a")?.text()) {
|
||||
"ONA" -> TvType.OVA
|
||||
else -> {
|
||||
type
|
||||
}
|
||||
}
|
||||
}
|
||||
"Status: " -> {
|
||||
showStatus = when (element.selectFirst("span")?.text()) {
|
||||
"Releasing" -> ShowStatus.Ongoing
|
||||
"Completed" -> ShowStatus.Completed
|
||||
else -> {
|
||||
showStatus
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class Result(
|
||||
@JsonProperty("url")
|
||||
val url: String? = null
|
||||
)
|
||||
|
||||
data class Links(
|
||||
@JsonProperty("result")
|
||||
val result: Result? = null
|
||||
)
|
||||
|
||||
//TODO 9anime outro into {"status":200,"result":{"url":"","skip_data":{"intro_begin":67,"intro_end":154,"outro_begin":1337,"outro_end":1415,"count":3}},"message":"","messages":[]}
|
||||
private suspend fun getEpisodeLinks(id: String): Links? {
|
||||
return app.get("$mainUrl/ajax/server/$id?vrf=${encodeVrf(id, cipherKey)}").parsedSafe()
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
val body = app.get(data).parsed<Response>().html
|
||||
val document = Jsoup.parse(body)
|
||||
|
||||
document.select("li").apmap {
|
||||
try {
|
||||
val name = it.text()
|
||||
val encodedStreamUrl =
|
||||
getEpisodeLinks(it.attr("data-link-id"))?.result?.url ?: return@apmap
|
||||
val url = decodeVrf(encodedStreamUrl, cipherKey)
|
||||
if (!loadExtractor(url, mainUrl, subtitleCallback, callback)) {
|
||||
callback(
|
||||
ExtractorLink(
|
||||
this.name,
|
||||
name,
|
||||
url,
|
||||
mainUrl,
|
||||
Qualities.Unknown.value,
|
||||
url.contains(".m3u8")
|
||||
)
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -1,257 +0,0 @@
|
|||
package com.lagradost.cloudstream3.animeproviders
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Element
|
||||
|
||||
class NontonAnimeIDProvider : MainAPI() {
|
||||
override var mainUrl = "https://75.119.159.228"
|
||||
override var name = "NontonAnimeID"
|
||||
override val hasQuickSearch = false
|
||||
override val hasMainPage = true
|
||||
override var lang = "id"
|
||||
override val hasDownloadSupport = true
|
||||
|
||||
override val supportedTypes = setOf(
|
||||
TvType.Anime,
|
||||
TvType.AnimeMovie,
|
||||
TvType.OVA
|
||||
)
|
||||
|
||||
companion object {
|
||||
fun getType(t: String): TvType {
|
||||
return when {
|
||||
t.contains("TV") -> TvType.Anime
|
||||
t.contains("Movie") -> TvType.AnimeMovie
|
||||
else -> TvType.OVA
|
||||
}
|
||||
}
|
||||
|
||||
fun getStatus(t: String): ShowStatus {
|
||||
return when (t) {
|
||||
"Finished Airing" -> ShowStatus.Completed
|
||||
"Currently Airing" -> ShowStatus.Ongoing
|
||||
else -> ShowStatus.Completed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse {
|
||||
val document = app.get(mainUrl).document
|
||||
|
||||
val homePageList = ArrayList<HomePageList>()
|
||||
|
||||
document.select("section#postbaru").forEach { block ->
|
||||
val header = block.selectFirst("h2")!!.text().trim()
|
||||
val animes = block.select("article.animeseries").map {
|
||||
it.toSearchResult()
|
||||
}
|
||||
if (animes.isNotEmpty()) homePageList.add(HomePageList(header, animes))
|
||||
}
|
||||
|
||||
document.select("aside#sidebar_right > div:nth-child(4)").forEach { block ->
|
||||
val header = block.selectFirst("h3")!!.ownText().trim()
|
||||
val animes = block.select("li.fullwdth").map {
|
||||
it.toSearchResultPopular()
|
||||
}
|
||||
if (animes.isNotEmpty()) homePageList.add(HomePageList(header, animes))
|
||||
}
|
||||
|
||||
return HomePageResponse(homePageList)
|
||||
}
|
||||
|
||||
private fun getProperAnimeLink(uri: String): String {
|
||||
return if (uri.contains("/anime/")) {
|
||||
uri
|
||||
} else {
|
||||
var title = uri.substringAfter("$mainUrl/")
|
||||
val fixTitle = Regex("(.*)-episode.*").find(title)?.groupValues?.getOrNull(1).toString()
|
||||
title = when {
|
||||
title.contains("utawarerumono-season-3") -> fixTitle.replace(
|
||||
"season-3",
|
||||
"futari-no-hakuoro"
|
||||
)
|
||||
title.contains("kingdom-season-4") -> fixTitle.replace("season-4", "4th-season")
|
||||
title.contains("maou-sama-season-2") -> fixTitle.replace("season-2", "2")
|
||||
title.contains("overlord-season-4") -> fixTitle.replace("season-4", "iv")
|
||||
title.contains("kyoushitsu-e-season-2") -> fixTitle.replace(
|
||||
"kyoushitsu-e-season-2",
|
||||
"kyoushitsu-e-tv-2nd-season"
|
||||
)
|
||||
title.contains("season-2") -> fixTitle.replace("season-2", "2nd-season")
|
||||
title.contains("season-3") -> fixTitle.replace("season-3", "3rd-season")
|
||||
title.contains("movie") -> title.substringBefore("-movie")
|
||||
else -> fixTitle
|
||||
}
|
||||
"$mainUrl/anime/$title"
|
||||
}
|
||||
}
|
||||
|
||||
private fun Element.toSearchResult(): AnimeSearchResponse {
|
||||
val href = getProperAnimeLink(fixUrl(this.selectFirst("a")!!.attr("href")))
|
||||
val title = this.selectFirst("h3.title")!!.text()
|
||||
val posterUrl = fixUrl(this.select("img").attr("data-src"))
|
||||
|
||||
return newAnimeSearchResponse(title, href, TvType.Anime) {
|
||||
this.posterUrl = posterUrl
|
||||
addDubStatus(dubExist = false, subExist = true)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun Element.toSearchResultPopular(): AnimeSearchResponse {
|
||||
val href = getProperAnimeLink(fixUrl(this.selectFirst("a")!!.attr("href")))
|
||||
val title = this.select("h4").text().trim()
|
||||
val posterUrl = fixUrl(this.select("img").attr("data-src"))
|
||||
|
||||
return newAnimeSearchResponse(title, href, TvType.Anime) {
|
||||
this.posterUrl = posterUrl
|
||||
addDubStatus(dubExist = false, subExist = true)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
val link = "$mainUrl/?s=$query"
|
||||
val document = app.get(link).document
|
||||
|
||||
return document.select(".result > ul > li").mapNotNull {
|
||||
val title = it.selectFirst("h2")!!.text().trim()
|
||||
val poster = it.selectFirst("img")!!.attr("src")
|
||||
val tvType = getType(
|
||||
it.selectFirst(".boxinfores > span.typeseries")!!.text().toString()
|
||||
)
|
||||
val href = fixUrl(it.selectFirst("a")!!.attr("href"))
|
||||
|
||||
newAnimeSearchResponse(title, href, tvType) {
|
||||
this.posterUrl = poster
|
||||
addDubStatus(dubExist = false, subExist = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private data class EpResponse(
|
||||
@JsonProperty("posts") val posts: String?,
|
||||
@JsonProperty("max_page") val max_page: Int?,
|
||||
@JsonProperty("found_posts") val found_posts: Int?,
|
||||
@JsonProperty("content") val content: String
|
||||
)
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val document = app.get(url).document
|
||||
|
||||
val title = document.selectFirst("h1.entry-title.cs")!!.text().trim()
|
||||
val poster = document.selectFirst(".poster > img")?.attr("data-src")
|
||||
val tags = document.select(".tagline > a").map { it.text() }
|
||||
|
||||
val year = Regex("\\d, ([0-9]*)").find(
|
||||
document.select(".bottomtitle > span:nth-child(5)").text()
|
||||
)?.groupValues?.get(1)?.toIntOrNull()
|
||||
val status = getStatus(
|
||||
document.select("span.statusseries").text().trim()
|
||||
)
|
||||
val type = getType(document.select("span.typeseries").text().trim())
|
||||
val rating = document.select("span.nilaiseries").text().trim().toIntOrNull()
|
||||
val description = document.select(".entry-content.seriesdesc > p").text().trim()
|
||||
val trailer = document.selectFirst("iframe#traileryt")?.attr("data-src")
|
||||
|
||||
val episodes = if (document.select("button.buttfilter").isNotEmpty()) {
|
||||
val id = document.select("input[name=series_id]").attr("value")
|
||||
val numEp =
|
||||
document.selectFirst(".latestepisode > a")?.text()?.replace(Regex("[^0-9]"), "")
|
||||
.toString()
|
||||
Jsoup.parse(
|
||||
app.post(
|
||||
url = "$mainUrl/wp-admin/admin-ajax.php",
|
||||
data = mapOf(
|
||||
"misha_number_of_results" to numEp,
|
||||
"misha_order_by" to "date-DESC",
|
||||
"action" to "mishafilter",
|
||||
"series_id" to id
|
||||
)
|
||||
).parsed<EpResponse>().content
|
||||
).select("li").map {
|
||||
val name = Regex("(Episode\\s?[0-9]+)").find(
|
||||
it.selectFirst("a")?.text().toString()
|
||||
)?.groupValues?.getOrNull(0) ?: it.selectFirst("a")?.text()
|
||||
val link = fixUrl(it.selectFirst("a")!!.attr("href"))
|
||||
Episode(link, name)
|
||||
}.reversed()
|
||||
} else {
|
||||
document.select("ul.misha_posts_wrap2 > li").map {
|
||||
val name = Regex("(Episode\\s?[0-9]+)").find(
|
||||
it.selectFirst("a")?.text().toString()
|
||||
)?.groupValues?.getOrNull(0) ?: it.selectFirst("a")?.text()
|
||||
val link = it.select("a").attr("href")
|
||||
Episode(link, name)
|
||||
}.reversed()
|
||||
}
|
||||
|
||||
|
||||
val recommendations = document.select(".result > li").mapNotNull {
|
||||
val epHref = it.selectFirst("a")!!.attr("href")
|
||||
val epTitle = it.selectFirst("h3")!!.text()
|
||||
val epPoster = it.select(".top > img").attr("data-src")
|
||||
|
||||
newAnimeSearchResponse(epTitle, epHref, TvType.Anime) {
|
||||
this.posterUrl = epPoster
|
||||
addDubStatus(dubExist = false, subExist = true)
|
||||
}
|
||||
}
|
||||
|
||||
return newAnimeLoadResponse(title, url, type) {
|
||||
engName = title
|
||||
posterUrl = poster
|
||||
this.year = year
|
||||
addEpisodes(DubStatus.Subbed, episodes)
|
||||
showStatus = status
|
||||
this.rating = rating
|
||||
plot = description
|
||||
addTrailer(trailer)
|
||||
this.tags = tags
|
||||
this.recommendations = recommendations
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
|
||||
val document = app.get(data).document
|
||||
val sources = ArrayList<String>()
|
||||
|
||||
document.select(".container1 > ul > li:not(.boxtab)").apmap {
|
||||
val dataPost = it.attr("data-post")
|
||||
val dataNume = it.attr("data-nume")
|
||||
val dataType = it.attr("data-type")
|
||||
|
||||
val iframe = app.post(
|
||||
url = "$mainUrl/wp-admin/admin-ajax.php",
|
||||
data = mapOf(
|
||||
"action" to "player_ajax",
|
||||
"post" to dataPost,
|
||||
"nume" to dataNume,
|
||||
"type" to dataType
|
||||
),
|
||||
referer = data,
|
||||
headers = mapOf("X-Requested-With" to "XMLHttpRequest")
|
||||
).document.select("iframe").attr("src")
|
||||
|
||||
sources.add(fixUrl(iframe))
|
||||
}
|
||||
|
||||
sources.apmap {
|
||||
loadExtractor(it, "$mainUrl/", subtitleCallback, callback)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -1,203 +0,0 @@
|
|||
package com.lagradost.cloudstream3.animeproviders
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Element
|
||||
import java.util.ArrayList
|
||||
|
||||
|
||||
class OploverzProvider : MainAPI() {
|
||||
override var mainUrl = "https://65.108.132.145"
|
||||
override var name = "Oploverz"
|
||||
override val hasMainPage = true
|
||||
override var lang = "id"
|
||||
override val hasDownloadSupport = true
|
||||
|
||||
override val supportedTypes = setOf(
|
||||
TvType.Anime,
|
||||
TvType.AnimeMovie,
|
||||
TvType.OVA
|
||||
)
|
||||
|
||||
companion object {
|
||||
fun getType(t: String): TvType {
|
||||
return when {
|
||||
t.contains("TV") -> TvType.Anime
|
||||
t.contains("Movie") -> TvType.AnimeMovie
|
||||
else -> TvType.OVA
|
||||
}
|
||||
}
|
||||
|
||||
fun getStatus(t: String): ShowStatus {
|
||||
return when (t) {
|
||||
"Completed" -> ShowStatus.Completed
|
||||
"Ongoing" -> ShowStatus.Ongoing
|
||||
else -> ShowStatus.Completed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val mainPage = mainPageOf(
|
||||
"&status=&type=&order=update" to "Episode Terbaru",
|
||||
"&status=&type=&order=latest" to "Anime Terbaru",
|
||||
"&sub=&order=popular" to "Popular Anime",
|
||||
)
|
||||
|
||||
override suspend fun getMainPage(
|
||||
page: Int,
|
||||
request: MainPageRequest
|
||||
): HomePageResponse {
|
||||
val document = app.get("$mainUrl/anime/?page=$page${request.data}").document
|
||||
val home = document.select("article[itemscope=itemscope]").mapNotNull {
|
||||
it.toSearchResult()
|
||||
}
|
||||
return newHomePageResponse(request.name, home)
|
||||
}
|
||||
|
||||
private fun getProperAnimeLink(uri: String): String {
|
||||
|
||||
return if (uri.contains("/anime/")) {
|
||||
uri
|
||||
} else {
|
||||
var title = uri.substringAfter("$mainUrl/")
|
||||
title = when {
|
||||
(title.contains("-episode")) && !(title.contains("-ova")) -> Regex("(.+)-episode").find(
|
||||
title
|
||||
)?.groupValues?.get(1).toString()
|
||||
(title.contains("-ova")) -> Regex("(.+)-ova").find(title)?.groupValues?.get(1)
|
||||
.toString()
|
||||
(title.contains("-movie")) -> Regex("(.+)-subtitle").find(title)?.groupValues?.get(1)
|
||||
.toString()
|
||||
else -> Regex("(.+)-subtitle").find(title)?.groupValues?.get(1).toString()
|
||||
.replace(Regex("-\\d+"), "")
|
||||
}
|
||||
|
||||
when {
|
||||
title.contains("overlord") -> {
|
||||
title = title.replace("s", "season-")
|
||||
}
|
||||
title.contains("kaguya-sama") -> {
|
||||
title = title.replace("s3", "ultra-romantic")
|
||||
}
|
||||
}
|
||||
|
||||
"$mainUrl/anime/$title"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun Element.toSearchResult(): AnimeSearchResponse? {
|
||||
val href = getProperAnimeLink(this.selectFirst("a.tip")!!.attr("href"))
|
||||
val title = this.selectFirst("h2[itemprop=headline]")?.text()?.trim() ?: return null
|
||||
val posterUrl = fixUrlNull(this.selectFirst("img")?.attr("src"))
|
||||
val type = getType(this.selectFirst(".eggtype, .typez")?.text()?.trim().toString())
|
||||
|
||||
return newAnimeSearchResponse(title, href, type) {
|
||||
this.posterUrl = posterUrl
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
val link = "$mainUrl/?s=$query"
|
||||
val document = app.get(link).document
|
||||
|
||||
return document.select("article[itemscope=itemscope]").map {
|
||||
val title = it.selectFirst(".tt")?.ownText()?.trim().toString()
|
||||
val poster = fixUrlNull(it.selectFirst("img")?.attr("src"))
|
||||
val tvType = getType(it.selectFirst(".typez")?.text().toString())
|
||||
val href = fixUrl(it.selectFirst("a.tip")!!.attr("href"))
|
||||
|
||||
newAnimeSearchResponse(title, href, tvType) {
|
||||
this.posterUrl = poster
|
||||
addDubStatus(dubExist = false, subExist = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val document = app.get(url).document
|
||||
|
||||
val title = document.selectFirst("h1.entry-title")!!.text().trim()
|
||||
val poster = document.select(".thumb > img").attr("src")
|
||||
val tags = document.select(".genxed > a").map { it.text() }
|
||||
|
||||
val year = Regex("\\d, ([0-9]*)").find(
|
||||
document.selectFirst(".info-content > .spe > span > time")!!.text().trim()
|
||||
)?.groupValues?.get(1).toString().toIntOrNull()
|
||||
val status = getStatus(
|
||||
document.select(".info-content > .spe > span:nth-child(1)")
|
||||
.text().trim().replace("Status: ", "")
|
||||
)
|
||||
val typeCheck =
|
||||
when (document.select(".info-content > .spe > span:nth-child(5), .info-content > .spe > span")
|
||||
.text().trim()) {
|
||||
"OVA" -> "OVA"
|
||||
"Movie" -> "Movie"
|
||||
else -> "TV"
|
||||
}
|
||||
val type = getType(typeCheck)
|
||||
val description = document.select(".entry-content > p").text().trim()
|
||||
val trailer = document.selectFirst("a.trailerbutton")?.attr("href")
|
||||
|
||||
val episodes = document.select(".eplister > ul > li").map {
|
||||
val header = it.select(".epl-title").text()
|
||||
val name =
|
||||
Regex("(Episode\\s?[0-9]+)").find(header)?.groupValues?.getOrNull(0) ?: header
|
||||
val link = fixUrl(it.select("a").attr("href"))
|
||||
Episode(link, name)
|
||||
}.reversed()
|
||||
|
||||
val recommendations =
|
||||
document.select(".listupd > article[itemscope=itemscope]").mapNotNull { rec ->
|
||||
val epTitle = rec.selectFirst(".tt")!!.ownText().trim()
|
||||
val epPoster = rec.selectFirst("img")!!.attr("src")
|
||||
val epType = getType(rec.selectFirst(".typez")?.text().toString())
|
||||
val epHref = fixUrl(rec.selectFirst("a.tip")!!.attr("href"))
|
||||
|
||||
newAnimeSearchResponse(epTitle, epHref, epType) {
|
||||
this.posterUrl = epPoster
|
||||
addDubStatus(dubExist = false, subExist = true)
|
||||
}
|
||||
}
|
||||
|
||||
return newAnimeLoadResponse(title, url, type) {
|
||||
engName = title
|
||||
posterUrl = poster
|
||||
this.year = year
|
||||
addEpisodes(DubStatus.Subbed, episodes)
|
||||
showStatus = status
|
||||
plot = description
|
||||
this.tags = tags
|
||||
this.recommendations = recommendations
|
||||
addTrailer(trailer)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
data class Source(
|
||||
@JsonProperty("play_url") val play_url: String,
|
||||
@JsonProperty("format_id") val format_id: Int
|
||||
)
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
val document = app.get(data).document
|
||||
val sources = document.select(".mobius > .mirror > option").mapNotNull {
|
||||
fixUrl(Jsoup.parse(base64Decode(it.attr("value"))).select("iframe").attr("src"))
|
||||
}
|
||||
|
||||
sources.apmap {
|
||||
loadExtractor(it, data, subtitleCallback, callback)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
|
@ -1,204 +0,0 @@
|
|||
package com.lagradost.cloudstream3.animeproviders
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Element
|
||||
import java.util.ArrayList
|
||||
|
||||
class OtakudesuProvider : MainAPI() {
|
||||
override var mainUrl = "https://otakudesu.watch"
|
||||
override var name = "Otakudesu"
|
||||
override val hasMainPage = true
|
||||
override var lang = "id"
|
||||
override val hasDownloadSupport = true
|
||||
|
||||
override val supportedTypes = setOf(
|
||||
TvType.Anime,
|
||||
TvType.AnimeMovie,
|
||||
TvType.OVA
|
||||
)
|
||||
|
||||
companion object {
|
||||
fun getType(t: String): TvType {
|
||||
return if (t.contains("OVA") || t.contains("Special")) TvType.OVA
|
||||
else if (t.contains("Movie")) TvType.AnimeMovie
|
||||
else TvType.Anime
|
||||
}
|
||||
|
||||
fun getStatus(t: String): ShowStatus {
|
||||
return when (t) {
|
||||
"Completed" -> ShowStatus.Completed
|
||||
"Ongoing" -> ShowStatus.Ongoing
|
||||
else -> ShowStatus.Completed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val mainPage = mainPageOf(
|
||||
"$mainUrl/ongoing-anime/page/" to "Anime Ongoing",
|
||||
"$mainUrl/complete-anime/page/" to "Anime Completed"
|
||||
)
|
||||
|
||||
override suspend fun getMainPage(
|
||||
page: Int,
|
||||
request: MainPageRequest
|
||||
): HomePageResponse {
|
||||
val document = app.get(request.data + page).document
|
||||
val home = document.select("div.venz > ul > li").mapNotNull {
|
||||
it.toSearchResult()
|
||||
}
|
||||
return newHomePageResponse(request.name, home)
|
||||
}
|
||||
|
||||
private fun Element.toSearchResult(): AnimeSearchResponse? {
|
||||
val title = this.selectFirst("h2.jdlflm")?.text()?.trim() ?: return null
|
||||
val href = this.selectFirst("a")!!.attr("href")
|
||||
val posterUrl = this.select("div.thumbz > img").attr("src").toString()
|
||||
val epNum = this.selectFirst("div.epz")?.ownText()?.replace(Regex("[^0-9]"), "")?.trim()
|
||||
?.toIntOrNull()
|
||||
return newAnimeSearchResponse(title, href, TvType.Anime) {
|
||||
this.posterUrl = posterUrl
|
||||
addSub(epNum)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
val link = "$mainUrl/?s=$query&post_type=anime"
|
||||
val document = app.get(link).document
|
||||
|
||||
return document.select("ul.chivsrc > li").map {
|
||||
val title = it.selectFirst("h2 > a")!!.ownText().trim()
|
||||
val href = it.selectFirst("h2 > a")!!.attr("href")
|
||||
val posterUrl = it.selectFirst("img")!!.attr("src").toString()
|
||||
newAnimeSearchResponse(title, href, TvType.Anime) {
|
||||
this.posterUrl = posterUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val document = app.get(url).document
|
||||
|
||||
val title = document.selectFirst("div.infozingle > p:nth-child(1) > span")?.ownText()
|
||||
?.replace(":", "")?.trim().toString()
|
||||
val poster = document.selectFirst("div.fotoanime > img")?.attr("src")
|
||||
val tags = document.select("div.infozingle > p:nth-child(11) > span > a").map { it.text() }
|
||||
val type = getType(
|
||||
document.selectFirst("div.infozingle > p:nth-child(5) > span")?.ownText()
|
||||
?.replace(":", "")?.trim().toString()
|
||||
)
|
||||
val year = Regex("\\d, ([0-9]*)").find(
|
||||
document.select("div.infozingle > p:nth-child(9) > span").text()
|
||||
)?.groupValues?.get(1)?.toIntOrNull()
|
||||
val status = getStatus(
|
||||
document.selectFirst("div.infozingle > p:nth-child(6) > span")!!.ownText()
|
||||
.replace(":", "")
|
||||
.trim()
|
||||
)
|
||||
val description = document.select("div.sinopc > p").text()
|
||||
|
||||
val episodes = document.select("div.episodelist")[1].select("ul > li").mapNotNull {
|
||||
val name = Regex("(Episode\\s?[0-9]+)").find(
|
||||
it.selectFirst("a")?.text().toString()
|
||||
)?.groupValues?.getOrNull(0) ?: it.selectFirst("a")?.text()
|
||||
val link = fixUrl(it.selectFirst("a")!!.attr("href"))
|
||||
Episode(link, name)
|
||||
}.reversed()
|
||||
|
||||
val recommendations =
|
||||
document.select("div.isi-recommend-anime-series > div.isi-konten").map {
|
||||
val recName = it.selectFirst("span.judul-anime > a")!!.text()
|
||||
val recHref = it.selectFirst("a")!!.attr("href")
|
||||
val recPosterUrl = it.selectFirst("a > img")?.attr("src").toString()
|
||||
newAnimeSearchResponse(recName, recHref, TvType.Anime) {
|
||||
this.posterUrl = recPosterUrl
|
||||
}
|
||||
}
|
||||
|
||||
return newAnimeLoadResponse(title, url, type) {
|
||||
engName = title
|
||||
posterUrl = poster
|
||||
this.year = year
|
||||
addEpisodes(DubStatus.Subbed, episodes)
|
||||
showStatus = status
|
||||
plot = description
|
||||
this.tags = tags
|
||||
this.recommendations = recommendations
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data class ResponseSources(
|
||||
@JsonProperty("id") val id: String,
|
||||
@JsonProperty("i") val i: String,
|
||||
@JsonProperty("q") val q: String,
|
||||
)
|
||||
|
||||
data class ResponseData(
|
||||
@JsonProperty("data") val data: String
|
||||
)
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
|
||||
val document = app.get(data).document
|
||||
val scriptData = document.select("script").last()?.data()
|
||||
val token = scriptData?.substringAfter("{action:\"")?.substringBefore("\"}").toString()
|
||||
|
||||
val nonce = app.post("$mainUrl/wp-admin/admin-ajax.php", data = mapOf("action" to token))
|
||||
.parsed<ResponseData>().data
|
||||
val action = scriptData?.substringAfter(",action:\"")?.substringBefore("\"}").toString()
|
||||
|
||||
val mirrorData = document.select("div.mirrorstream > ul > li").mapNotNull {
|
||||
base64Decode(it.select("a").attr("data-content"))
|
||||
}.toString()
|
||||
|
||||
tryParseJson<List<ResponseSources>>(mirrorData)?.apmap { res ->
|
||||
val id = res.id
|
||||
val i = res.i
|
||||
val q = res.q
|
||||
|
||||
var sources = Jsoup.parse(
|
||||
base64Decode(
|
||||
app.post(
|
||||
"${mainUrl}/wp-admin/admin-ajax.php", data = mapOf(
|
||||
"id" to id,
|
||||
"i" to i,
|
||||
"q" to q,
|
||||
"nonce" to nonce,
|
||||
"action" to action
|
||||
)
|
||||
).parsed<ResponseData>().data
|
||||
)
|
||||
).select("iframe").attr("src")
|
||||
|
||||
if (sources.startsWith("https://desustream.me")) {
|
||||
if (!sources.contains("/arcg/") && !sources.contains("/odchan/") && !sources.contains(
|
||||
"/desudrive/"
|
||||
)
|
||||
) {
|
||||
sources = app.get(sources).document.select("iframe").attr("src")
|
||||
}
|
||||
if (sources.startsWith("https://yourupload.com")) {
|
||||
sources = sources.replace("//", "//www.")
|
||||
}
|
||||
}
|
||||
|
||||
loadExtractor(sources, data, subtitleCallback, callback)
|
||||
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
|
@ -1,352 +0,0 @@
|
|||
package com.lagradost.cloudstream3.animeproviders
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.network.DdosGuardKiller
|
||||
import com.lagradost.cloudstream3.network.getHeaders
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||
import org.jsoup.nodes.Document
|
||||
import java.net.URI
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
class TenshiProvider : MainAPI() {
|
||||
companion object {
|
||||
//var token: String? = null
|
||||
//var cookie: Map<String, String> = mapOf()
|
||||
|
||||
fun getType(t: String): TvType {
|
||||
return if (t.contains("OVA") || t.contains("Special")) TvType.OVA
|
||||
else if (t.contains("Movie")) TvType.AnimeMovie
|
||||
else TvType.Anime
|
||||
}
|
||||
}
|
||||
|
||||
override var mainUrl = "https://tenshi.moe"
|
||||
override var name = "Tenshi.moe"
|
||||
override val hasQuickSearch = false
|
||||
override val hasMainPage = true
|
||||
override val supportedTypes = setOf(TvType.Anime, TvType.AnimeMovie, TvType.OVA)
|
||||
private var ddosGuardKiller = DdosGuardKiller(true)
|
||||
|
||||
/*private fun loadToken(): Boolean {
|
||||
return try {
|
||||
val response = get(mainUrl)
|
||||
cookie = response.cookies
|
||||
val document = Jsoup.parse(response.text)
|
||||
token = document.selectFirst("""meta[name="csrf-token"]""").attr("content")
|
||||
token != null
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
}*/
|
||||
|
||||
override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse {
|
||||
val items = ArrayList<HomePageList>()
|
||||
val soup = app.get(mainUrl, interceptor = ddosGuardKiller).document
|
||||
for (section in soup.select("#content > section")) {
|
||||
try {
|
||||
if (section.attr("id") == "toplist-tabs") {
|
||||
for (top in section.select(".tab-content > [role=\"tabpanel\"]")) {
|
||||
val title = "Top - " + top.attr("id").split("-")[1].replaceFirstChar {
|
||||
if (it.isLowerCase()) it.titlecase(
|
||||
Locale.UK
|
||||
) else it.toString()
|
||||
}
|
||||
val anime = top.select("li > a").map {
|
||||
AnimeSearchResponse(
|
||||
it.selectFirst(".thumb-title")!!.text(),
|
||||
fixUrl(it.attr("href")),
|
||||
this.name,
|
||||
TvType.Anime,
|
||||
it.selectFirst("img")!!.attr("src"),
|
||||
null,
|
||||
EnumSet.of(DubStatus.Subbed),
|
||||
)
|
||||
}
|
||||
items.add(HomePageList(title, anime))
|
||||
}
|
||||
} else {
|
||||
val title = section.selectFirst("h2")!!.text()
|
||||
val anime = section.select("li > a").map {
|
||||
AnimeSearchResponse(
|
||||
it.selectFirst(".thumb-title")?.text() ?: "",
|
||||
fixUrl(it.attr("href")),
|
||||
this.name,
|
||||
TvType.Anime,
|
||||
it.selectFirst("img")!!.attr("src"),
|
||||
null,
|
||||
EnumSet.of(DubStatus.Subbed),
|
||||
)
|
||||
}
|
||||
items.add(HomePageList(title, anime))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
if (items.size <= 0) throw ErrorLoadingException()
|
||||
return HomePageResponse(items)
|
||||
}
|
||||
|
||||
private fun getIsMovie(type: String, id: Boolean = false): Boolean {
|
||||
if (!id) return type == "Movie"
|
||||
|
||||
val movies = listOf("rrso24fa", "e4hqvtym", "bl5jdbqn", "u4vtznut", "37t6h2r4", "cq4azcrj")
|
||||
val aniId = type.replace("$mainUrl/anime/", "")
|
||||
return movies.contains(aniId)
|
||||
}
|
||||
|
||||
private fun parseSearchPage(soup: Document): List<SearchResponse> {
|
||||
val items = soup.select("ul.thumb > li > a")
|
||||
return items.map {
|
||||
val href = fixUrl(it.attr("href"))
|
||||
val img = fixUrl(it.selectFirst("img")!!.attr("src"))
|
||||
val title = it.attr("title")
|
||||
if (getIsMovie(href, true)) {
|
||||
MovieSearchResponse(
|
||||
title, href, this.name, TvType.Movie, img, null
|
||||
)
|
||||
} else {
|
||||
AnimeSearchResponse(
|
||||
title,
|
||||
href,
|
||||
this.name,
|
||||
TvType.Anime,
|
||||
img,
|
||||
null,
|
||||
EnumSet.of(DubStatus.Subbed),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
private fun dateParser(dateString: String?): Date? {
|
||||
if (dateString == null) return null
|
||||
try {
|
||||
val format = SimpleDateFormat("dd 'of' MMM',' yyyy")
|
||||
val data = format.parse(
|
||||
dateString.replace("th ", " ").replace("st ", " ").replace("nd ", " ")
|
||||
.replace("rd ", " ")
|
||||
) ?: return null
|
||||
return data
|
||||
} catch (e: Exception) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// data class TenshiSearchResponse(
|
||||
// @JsonProperty("url") var url : String,
|
||||
// @JsonProperty("title") var title : String,
|
||||
// @JsonProperty("cover") var cover : String,
|
||||
// @JsonProperty("genre") var genre : String,
|
||||
// @JsonProperty("year") var year : Int,
|
||||
// @JsonProperty("type") var type : String,
|
||||
// @JsonProperty("eps") var eps : String,
|
||||
// @JsonProperty("cen") var cen : String
|
||||
// )
|
||||
|
||||
// override suspend fun quickSearch(query: String): ArrayList<SearchResponse>? {
|
||||
// if (!autoLoadToken()) return quickSearch(query)
|
||||
// val url = "$mainUrl/anime/search"
|
||||
// val response = khttp.post(
|
||||
// url,
|
||||
// data=mapOf("q" to query),
|
||||
// headers=mapOf("x-csrf-token" to token, "x-requested-with" to "XMLHttpRequest"),
|
||||
// cookies = cookie
|
||||
//
|
||||
// )
|
||||
//
|
||||
// val items = mapper.readValue<List<TenshiSearchResponse>>(response.text)
|
||||
//
|
||||
// if (items.isEmpty()) return ArrayList()
|
||||
//
|
||||
// val returnValue = ArrayList<SearchResponse>()
|
||||
// for (i in items) {
|
||||
// val href = fixUrl(i.url)
|
||||
// val title = i.title
|
||||
// val img = fixUrl(i.cover)
|
||||
// val year = i.year
|
||||
//
|
||||
// returnValue.add(
|
||||
// if (getIsMovie(i.type)) {
|
||||
// MovieSearchResponse(
|
||||
// title, href, getSlug(href), this.name, TvType.Movie, img, year
|
||||
// )
|
||||
// } else {
|
||||
// AnimeSearchResponse(
|
||||
// title, href, getSlug(href), this.name,
|
||||
// TvType.Anime, img, year, null,
|
||||
// EnumSet.of(DubStatus.Subbed),
|
||||
// null, null
|
||||
// )
|
||||
// }
|
||||
// )
|
||||
// }
|
||||
// return returnValue
|
||||
// }
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
val url = "$mainUrl/anime"
|
||||
var document = app.get(
|
||||
url,
|
||||
params = mapOf("q" to query),
|
||||
cookies = mapOf("loop-view" to "thumb"),
|
||||
interceptor = ddosGuardKiller
|
||||
).document
|
||||
|
||||
val returnValue = parseSearchPage(document).toMutableList()
|
||||
|
||||
while (!document.select("""a.page-link[rel="next"]""").isEmpty()) {
|
||||
val link = document.selectFirst("""a.page-link[rel="next"]""")?.attr("href")
|
||||
if (!link.isNullOrBlank()) {
|
||||
document = app.get(
|
||||
link,
|
||||
cookies = mapOf("loop-view" to "thumb"),
|
||||
interceptor = ddosGuardKiller
|
||||
).document
|
||||
returnValue.addAll(parseSearchPage(document))
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return returnValue
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
var document = app.get(
|
||||
url,
|
||||
cookies = mapOf("loop-view" to "thumb"),
|
||||
interceptor = ddosGuardKiller
|
||||
).document
|
||||
|
||||
val canonicalTitle = document.selectFirst("header.entry-header > h1.mb-3")!!.text().trim()
|
||||
val episodeNodes = document.select("li[class*=\"episode\"] > a").toMutableList()
|
||||
val totalEpisodePages = if (document.select(".pagination").size > 0)
|
||||
document.select(".pagination .page-item a.page-link:not([rel])").last()!!.text()
|
||||
.toIntOrNull()
|
||||
else 1
|
||||
|
||||
if (totalEpisodePages != null && totalEpisodePages > 1) {
|
||||
for (pageNum in 2..totalEpisodePages) {
|
||||
document = app.get(
|
||||
"$url?page=$pageNum",
|
||||
cookies = mapOf("loop-view" to "thumb"),
|
||||
interceptor = ddosGuardKiller
|
||||
).document
|
||||
episodeNodes.addAll(document.select("li[class*=\"episode\"] > a"))
|
||||
}
|
||||
}
|
||||
|
||||
val episodes = ArrayList(episodeNodes.map {
|
||||
val title = it.selectFirst(".episode-title")?.text()?.trim()
|
||||
newEpisode(it.attr("href")) {
|
||||
this.name = if (title == "No Title") null else title
|
||||
this.posterUrl = it.selectFirst("img")?.attr("src")
|
||||
addDate(dateParser(it?.selectFirst(".episode-date")?.text()?.trim()))
|
||||
this.description = it.attr("data-content").trim()
|
||||
}
|
||||
})
|
||||
|
||||
val similarAnime = document.select("ul.anime-loop > li > a").mapNotNull { element ->
|
||||
val href = element.attr("href") ?: return@mapNotNull null
|
||||
val title =
|
||||
element.selectFirst("> .overlay > .thumb-title")?.text() ?: return@mapNotNull null
|
||||
val img = element.selectFirst("> img")?.attr("src")
|
||||
AnimeSearchResponse(title, href, this.name, TvType.Anime, img)
|
||||
}
|
||||
|
||||
val type = document.selectFirst("a[href*=\"$mainUrl/type/\"]")?.text()?.trim()
|
||||
|
||||
return newAnimeLoadResponse(canonicalTitle, url, getType(type ?: "")) {
|
||||
recommendations = similarAnime
|
||||
posterUrl = document.selectFirst("img.cover-image")?.attr("src")
|
||||
plot = document.selectFirst(".entry-description > .card-body")?.text()?.trim()
|
||||
tags =
|
||||
document.select("li.genre.meta-data > span.value")
|
||||
.map { it?.text()?.trim().toString() }
|
||||
|
||||
synonyms =
|
||||
document.select("li.synonym.meta-data > div.info-box > span.value")
|
||||
.map { it?.text()?.trim().toString() }
|
||||
|
||||
engName =
|
||||
document.selectFirst("span.value > span[title=\"English\"]")?.parent()?.text()
|
||||
?.trim()
|
||||
japName =
|
||||
document.selectFirst("span.value > span[title=\"Japanese\"]")?.parent()?.text()
|
||||
?.trim()
|
||||
|
||||
val pattern = Regex("(\\d{4})")
|
||||
val yearText = document.selectFirst("li.release-date .value")!!.text()
|
||||
year = pattern.find(yearText)?.groupValues?.get(1)?.toIntOrNull()
|
||||
|
||||
addEpisodes(DubStatus.Subbed, episodes)
|
||||
|
||||
showStatus = when (document.selectFirst("li.status > .value")?.text()?.trim()) {
|
||||
"Ongoing" -> ShowStatus.Ongoing
|
||||
"Completed" -> ShowStatus.Completed
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
val soup = app.get(data, interceptor = ddosGuardKiller).document
|
||||
|
||||
data class Quality(
|
||||
@JsonProperty("src") val src: String,
|
||||
@JsonProperty("size") val size: Int
|
||||
)
|
||||
|
||||
for (source in soup.select("""[aria-labelledby="mirror-dropdown"] > li > a.dropdown-item""")) {
|
||||
val release = source.text().replace("/", "").trim()
|
||||
val sourceHTML = app.get(
|
||||
"https://tenshi.moe/embed?v=${source.attr("href").split("v=")[1].split("&")[0]}",
|
||||
headers = mapOf("Referer" to data), interceptor = ddosGuardKiller
|
||||
).text
|
||||
|
||||
val match = Regex("""sources: (\[(?:.|\s)+?type: ['"]video/.*?['"](?:.|\s)+?])""").find(
|
||||
sourceHTML
|
||||
)
|
||||
if (match != null) {
|
||||
val qualities = parseJson<List<Quality>>(
|
||||
match.destructured.component1()
|
||||
.replace("'", "\"")
|
||||
.replace(Regex("""(\w+): """), "\"\$1\": ")
|
||||
.replace(Regex("""\s+"""), "")
|
||||
.replace(",}", "}")
|
||||
.replace(",]", "]")
|
||||
)
|
||||
qualities.forEach {
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
this.name,
|
||||
"${this.name} $release",
|
||||
fixUrl(it.src),
|
||||
this.mainUrl,
|
||||
getQualityFromName("${it.size}"),
|
||||
headers = getHeaders(emptyMap(),
|
||||
ddosGuardKiller.savedCookiesMap[URI(this.mainUrl).host]
|
||||
?: emptyMap()
|
||||
).toMap()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -1,178 +0,0 @@
|
|||
package com.lagradost.cloudstream3.animeproviders
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
import org.jsoup.nodes.Element
|
||||
import java.util.*
|
||||
|
||||
class TocanimeProvider : MainAPI() {
|
||||
override var mainUrl = "https://tocanime.co"
|
||||
override var name = "Tocanime"
|
||||
override val hasMainPage = true
|
||||
override var lang = "vi"
|
||||
override val hasDownloadSupport = true
|
||||
|
||||
override val supportedTypes = setOf(
|
||||
TvType.Anime,
|
||||
TvType.AnimeMovie,
|
||||
TvType.OVA
|
||||
)
|
||||
|
||||
companion object {
|
||||
fun getType(t: String): TvType {
|
||||
return when {
|
||||
t.contains("OVA") || t.contains("Special") -> TvType.OVA
|
||||
t.contains("Movie") -> TvType.AnimeMovie
|
||||
else -> TvType.Anime
|
||||
}
|
||||
}
|
||||
|
||||
fun getStatus(t: String): ShowStatus {
|
||||
return when (t) {
|
||||
"Đã hoàn thành" -> ShowStatus.Completed
|
||||
"Chưa hoàn thành" -> ShowStatus.Ongoing
|
||||
else -> ShowStatus.Completed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse {
|
||||
val document = app.get(mainUrl).document
|
||||
|
||||
val homePageList = ArrayList<HomePageList>()
|
||||
|
||||
document.select("div#playlists > div").forEach { block ->
|
||||
val header = block.selectFirst("h2")?.text()?.trim() ?: ""
|
||||
val items = block.select("div.col-lg-3.col-md-4.col-6").map {
|
||||
it.toSearchResult()
|
||||
}
|
||||
if (items.isNotEmpty()) homePageList.add(HomePageList(header, items))
|
||||
}
|
||||
|
||||
return HomePageResponse(homePageList)
|
||||
}
|
||||
|
||||
private fun Element.toSearchResult(): AnimeSearchResponse {
|
||||
val title = this.selectFirst("h3 a")?.text()?.trim() ?: ""
|
||||
val href = fixUrl(this.selectFirst("h3 a")!!.attr("href"))
|
||||
val posterUrl = fixUrlNull(this.selectFirst("div.card-item-img")?.attr("data-original"))
|
||||
val epNum = this.selectFirst("div.card-item-badget.rtl")?.text()?.let { eps ->
|
||||
val num = eps.filter { it.isDigit() }.toIntOrNull()
|
||||
if(eps.contains("Preview")) {
|
||||
num?.minus(1)
|
||||
} else {
|
||||
num
|
||||
}
|
||||
}
|
||||
return newAnimeSearchResponse(title, href, TvType.Anime) {
|
||||
this.posterUrl = posterUrl
|
||||
addSub(epNum)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
val document = app.get("$mainUrl/content/search?t=kw&q=$query").document
|
||||
|
||||
return document.select("div.col-lg-3.col-md-4.col-6").map {
|
||||
it.toSearchResult()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse? {
|
||||
val document = app.get(url).document
|
||||
|
||||
val title = document.selectFirst("h1.title")?.text() ?: return null
|
||||
val type =
|
||||
if (document.select("div.me-list.scroller a").size == 1) TvType.AnimeMovie else TvType.Anime
|
||||
val episodes = document.select("div.me-list.scroller a").mapNotNull {
|
||||
Episode(fixUrl(it.attr("href")), it.text())
|
||||
}.reversed()
|
||||
val trailer =
|
||||
document.selectFirst("div#trailer script")?.data()?.substringAfter("<iframe src=\"")
|
||||
?.substringBefore("\"")
|
||||
|
||||
return newAnimeLoadResponse(title, url, type) {
|
||||
posterUrl = fixUrlNull(document.selectFirst("img.img")?.attr("data-original"))
|
||||
year = document.select("dl.movie-des dd")[1].text().split("/").last().toIntOrNull()
|
||||
showStatus = getStatus(
|
||||
document.select("dl.movie-des dd")[0].text()
|
||||
.toString()
|
||||
)
|
||||
plot = document.select("div.box-content > p").text()
|
||||
tags = document.select("dl.movie-des dd")[4].select("li")
|
||||
.map { it.select("a").text().removeSuffix(",").trim() }
|
||||
recommendations =
|
||||
document.select("div.col-lg-3.col-md-4.col-6").map { it.toSearchResult() }
|
||||
addEpisodes(DubStatus.Subbed, episodes)
|
||||
addTrailer(trailer)
|
||||
}
|
||||
}
|
||||
|
||||
private fun encode(input: String): String? = java.net.URLEncoder.encode(input, "utf-8")
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
|
||||
val document = app.get(
|
||||
data,
|
||||
headers = mapOf("Accept" to "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8"),
|
||||
).document
|
||||
|
||||
document.select("script").apmap { script ->
|
||||
if (script.data().contains("var PnPlayer=")) {
|
||||
val key = script.data().substringAfter("\"btsurl\":[[").substringBefore("]}]")
|
||||
.replace("]", "").replace("\"", "").split(",")
|
||||
val id = data.split("_").last().substringBefore(".html")
|
||||
|
||||
app.get(
|
||||
url = "$mainUrl/content/parseUrl?v=2&len=0&prefer=&ts=${Date().time}&item_id=$id&username=$id&sv=btsurl&${
|
||||
encode(
|
||||
"bts_url[]"
|
||||
)
|
||||
}=${
|
||||
encode(
|
||||
key.first()
|
||||
)
|
||||
}&sig=${key.last()}",
|
||||
referer = data,
|
||||
headers = mapOf(
|
||||
"Accept" to "application/json, text/javascript, */*; q=0.01",
|
||||
"X-Requested-With" to "XMLHttpRequest"
|
||||
)
|
||||
).parsedSafe<Responses>()?.let { res ->
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
source = this.name,
|
||||
name = this.name,
|
||||
url = res.formats?.auto ?: return@let,
|
||||
referer = "$mainUrl/",
|
||||
quality = Qualities.Unknown.value,
|
||||
isM3u8 = true
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
data class Formats(
|
||||
@JsonProperty("auto") val auto: String?,
|
||||
)
|
||||
|
||||
data class Responses(
|
||||
@JsonProperty("formats") val formats: Formats?,
|
||||
)
|
||||
|
||||
}
|
|
@ -1,270 +0,0 @@
|
|||
package com.lagradost.cloudstream3.animeproviders
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
import org.jsoup.Jsoup
|
||||
import org.mozilla.javascript.Context
|
||||
import org.mozilla.javascript.Scriptable
|
||||
import java.util.*
|
||||
|
||||
|
||||
class WatchCartoonOnlineProvider : MainAPI() {
|
||||
override var name = "WatchCartoonOnline"
|
||||
override var mainUrl = "https://www.wcostream.com"
|
||||
|
||||
override val supportedTypes = setOf(
|
||||
TvType.Cartoon,
|
||||
TvType.Anime,
|
||||
TvType.AnimeMovie,
|
||||
TvType.TvSeries
|
||||
)
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
val url = "https://www.wcostream.com/search"
|
||||
|
||||
var response =
|
||||
app.post(
|
||||
url,
|
||||
headers = mapOf("Referer" to url),
|
||||
data = mapOf("catara" to query, "konuara" to "series")
|
||||
).text
|
||||
var document = Jsoup.parse(response)
|
||||
var items = document.select("div#blog > div.cerceve").toList()
|
||||
|
||||
val returnValue = ArrayList<SearchResponse>()
|
||||
|
||||
for (item in items) {
|
||||
val header = item.selectFirst("> div.iccerceve")
|
||||
val titleHeader = header!!.selectFirst("> div.aramadabaslik > a")
|
||||
val title = titleHeader!!.text()
|
||||
val href = fixUrl(titleHeader.attr("href"))
|
||||
val poster = fixUrl(header.selectFirst("> a > img")!!.attr("src"))
|
||||
val genreText = item.selectFirst("div.cerceve-tur-ve-genre")!!.ownText()
|
||||
if (genreText.contains("cartoon")) {
|
||||
returnValue.add(TvSeriesSearchResponse(title, href, this.name, TvType.Cartoon, poster, null, null))
|
||||
} else {
|
||||
val isDubbed = genreText.contains("dubbed")
|
||||
val set: EnumSet<DubStatus> =
|
||||
EnumSet.of(if (isDubbed) DubStatus.Dubbed else DubStatus.Subbed)
|
||||
returnValue.add(
|
||||
AnimeSearchResponse(
|
||||
title,
|
||||
href,
|
||||
this.name,
|
||||
TvType.Anime,
|
||||
poster,
|
||||
null,
|
||||
set,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// "episodes-search", is used for finding movies, anime episodes should be filtered out
|
||||
response =
|
||||
app.post(
|
||||
url,
|
||||
headers = mapOf("Referer" to url),
|
||||
data = mapOf("catara" to query, "konuara" to "episodes")
|
||||
).text
|
||||
document = Jsoup.parse(response)
|
||||
items = document.select("#catlist-listview2 > ul > li")
|
||||
.filter { it -> it?.text() != null && !it.text().toString().contains("Episode") }
|
||||
|
||||
for (item in items) {
|
||||
val titleHeader = item.selectFirst("a")
|
||||
val title = titleHeader!!.text()
|
||||
val href = fixUrl(titleHeader.attr("href"))
|
||||
//val isDubbed = title.contains("dubbed")
|
||||
//val set: EnumSet<DubStatus> =
|
||||
// EnumSet.of(if (isDubbed) DubStatus.Dubbed else DubStatus.Subbed)
|
||||
returnValue.add(
|
||||
TvSeriesSearchResponse(
|
||||
title,
|
||||
href,
|
||||
this.name,
|
||||
TvType.AnimeMovie,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return returnValue
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val isMovie = !url.contains("/anime/")
|
||||
val response = app.get(url).text
|
||||
val document = Jsoup.parse(response)
|
||||
|
||||
return if (!isMovie) {
|
||||
val title = document.selectFirst("td.vsbaslik > h2")!!.text()
|
||||
val poster = fixUrlNull(document.selectFirst("div#cat-img-desc > div > img")?.attr("src"))
|
||||
val plot = document.selectFirst("div.iltext")!!.text()
|
||||
val genres = document.select("div#cat-genre > div.wcobtn > a").map { it.text() }
|
||||
val episodes = document.select("div#catlist-listview > ul > li > a").reversed().map {
|
||||
val text = it.text()
|
||||
val match = Regex("Season ([0-9]*) Episode ([0-9]*).*? (.*)").find(text)
|
||||
val href = it.attr("href")
|
||||
if (match != null) {
|
||||
val last = match.groupValues[3]
|
||||
return@map Episode(
|
||||
href,
|
||||
if (last.startsWith("English")) null else last,
|
||||
match.groupValues[1].toIntOrNull(),
|
||||
match.groupValues[2].toIntOrNull(),
|
||||
)
|
||||
}
|
||||
val match2 = Regex("Episode ([0-9]*).*? (.*)").find(text)
|
||||
if (match2 != null) {
|
||||
val last = match2.groupValues[2]
|
||||
return@map Episode(
|
||||
href,
|
||||
if (last.startsWith("English")) null else last,
|
||||
null,
|
||||
match2.groupValues[1].toIntOrNull(),
|
||||
)
|
||||
}
|
||||
return@map Episode(
|
||||
href,
|
||||
text
|
||||
)
|
||||
}
|
||||
TvSeriesLoadResponse(
|
||||
title,
|
||||
url,
|
||||
this.name,
|
||||
TvType.TvSeries,
|
||||
episodes,
|
||||
poster,
|
||||
null,
|
||||
plot,
|
||||
null,
|
||||
null,
|
||||
tags = genres
|
||||
)
|
||||
} else {
|
||||
val title = document.selectFirst(".iltext .Apple-style-span")?.text().toString()
|
||||
val b = document.select(".iltext b")
|
||||
val description = if (b.isNotEmpty()) {
|
||||
b.last()!!.html().split("<br>")[0]
|
||||
} else null
|
||||
|
||||
TvSeriesLoadResponse(
|
||||
title,
|
||||
url,
|
||||
this.name,
|
||||
TvType.TvSeries,
|
||||
listOf(Episode(url,title)),
|
||||
null,
|
||||
null,
|
||||
description,
|
||||
null,
|
||||
null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class LinkResponse(
|
||||
// @JsonProperty("cdn")
|
||||
// val cdn: String,
|
||||
@JsonProperty("enc")
|
||||
val enc: String,
|
||||
@JsonProperty("hd")
|
||||
val hd: String,
|
||||
@JsonProperty("server")
|
||||
val server: String,
|
||||
)
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
val response = app.get(data).text
|
||||
/*val embedUrl = fixUrl(
|
||||
Regex("itemprop=\"embedURL\" content=\"(.*?)\"").find(response.text)?.groupValues?.get(1) ?: return false
|
||||
)*/
|
||||
val start = response.indexOf("itemprop=\"embedURL")
|
||||
val foundJS = Regex("<script>(.*?)</script>").find(response, start)?.groupValues?.get(1)
|
||||
?.replace("document.write", "var returnValue = ")
|
||||
|
||||
val rhino = Context.enter()
|
||||
rhino.initStandardObjects()
|
||||
rhino.optimizationLevel = -1
|
||||
val scope: Scriptable = rhino.initStandardObjects()
|
||||
|
||||
val decodeBase64 = "atob = function(s) {\n" +
|
||||
" var e={},i,b=0,c,x,l=0,a,r='',w=String.fromCharCode,L=s.length;\n" +
|
||||
" var A=\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n" +
|
||||
" for(i=0;i<64;i++){e[A.charAt(i)]=i;}\n" +
|
||||
" for(x=0;x<L;x++){\n" +
|
||||
" c=e[s.charAt(x)];b=(b<<6)+c;l+=6;\n" +
|
||||
" while(l>=8){((a=(b>>>(l-=8))&0xff)||(x<(L-2)))&&(r+=w(a));}\n" +
|
||||
" }\n" +
|
||||
" return r;\n" +
|
||||
"};"
|
||||
|
||||
rhino.evaluateString(scope, decodeBase64 + foundJS, "JavaScript", 1, null)
|
||||
val jsEval = scope.get("returnValue", scope) ?: return false
|
||||
val src = fixUrl(Regex("src=\"(.*?)\"").find(jsEval as String)?.groupValues?.get(1) ?: return false)
|
||||
|
||||
val embedResponse = app.get(
|
||||
(src),
|
||||
headers = mapOf("Referer" to data)
|
||||
)
|
||||
|
||||
val getVidLink = fixUrl(
|
||||
Regex("get\\(\"(.*?)\"").find(embedResponse.text)?.groupValues?.get(1) ?: return false
|
||||
)
|
||||
val linkResponse = app.get(
|
||||
getVidLink, headers = mapOf(
|
||||
"sec-ch-ua" to "\"Chromium\";v=\"91\", \" Not;A Brand\";v=\"99\"",
|
||||
"sec-ch-ua-mobile" to "?0",
|
||||
"sec-fetch-dest" to "empty",
|
||||
"sec-fetch-mode" to "cors",
|
||||
"sec-fetch-site" to "same-origin",
|
||||
"accept" to "*/*",
|
||||
"x-requested-with" to "XMLHttpRequest",
|
||||
"referer" to src.replace(" ", "%20"),
|
||||
"user-agent" to "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
|
||||
"cookie" to "countrytabs=0"
|
||||
)
|
||||
)
|
||||
|
||||
val link = parseJson<LinkResponse>(linkResponse.text)
|
||||
|
||||
val hdLink = "${link.server}/getvid?evid=${link.hd}"
|
||||
val sdLink = "${link.server}/getvid?evid=${link.enc}"
|
||||
|
||||
if (link.hd.isNotBlank())
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
this.name,
|
||||
this.name + " HD",
|
||||
hdLink,
|
||||
"",
|
||||
Qualities.P720.value
|
||||
)
|
||||
)
|
||||
|
||||
if (link.enc.isNotBlank())
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
this.name,
|
||||
this.name + " SD",
|
||||
sdLink,
|
||||
"",
|
||||
Qualities.P480.value
|
||||
)
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -1,240 +0,0 @@
|
|||
package com.lagradost.cloudstream3.animeproviders
|
||||
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.extractors.Mcloud
|
||||
import com.lagradost.cloudstream3.extractors.WcoStream
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import org.json.JSONObject
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Document
|
||||
import java.util.*
|
||||
|
||||
|
||||
class WcoProvider : MainAPI() {
|
||||
companion object {
|
||||
fun getType(t: String): TvType {
|
||||
return if (t.contains("OVA") || t.contains("Special")) TvType.OVA
|
||||
else if (t.contains("Movie")) TvType.AnimeMovie
|
||||
else TvType.Anime
|
||||
}
|
||||
}
|
||||
|
||||
override var mainUrl = "https://wcostream.cc"
|
||||
override var name = "WCO Stream"
|
||||
override val hasQuickSearch = true
|
||||
override val hasMainPage = true
|
||||
|
||||
override val supportedTypes = setOf(
|
||||
TvType.AnimeMovie,
|
||||
TvType.Anime,
|
||||
TvType.OVA
|
||||
)
|
||||
|
||||
override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse {
|
||||
val urls = listOf(
|
||||
Pair("$mainUrl/ajax/list/recently_updated?type=tv", "Recently Updated Anime"),
|
||||
Pair("$mainUrl/ajax/list/recently_updated?type=movie", "Recently Updated Movies"),
|
||||
Pair("$mainUrl/ajax/list/recently_added?type=tv", "Recently Added Anime"),
|
||||
Pair("$mainUrl/ajax/list/recently_added?type=movie", "Recently Added Movies"),
|
||||
)
|
||||
|
||||
val items = ArrayList<HomePageList>()
|
||||
for (i in urls) {
|
||||
try {
|
||||
val response = JSONObject(
|
||||
app.get(
|
||||
i.first,
|
||||
).text
|
||||
).getString("html") // I won't make a dataclass for this shit
|
||||
val document = Jsoup.parse(response)
|
||||
val results = document.select("div.flw-item").map {
|
||||
val filmPoster = it.selectFirst("> div.film-poster")
|
||||
val filmDetail = it.selectFirst("> div.film-detail")
|
||||
val nameHeader = filmDetail!!.selectFirst("> h3.film-name > a")
|
||||
val title = nameHeader!!.text().replace(" (Dub)", "")
|
||||
val href =
|
||||
nameHeader.attr("href").replace("/watch/", "/anime/")
|
||||
.replace(Regex("-episode-.*"), "/")
|
||||
val isDub =
|
||||
filmPoster!!.selectFirst("> div.film-poster-quality")?.text()
|
||||
?.contains("DUB")
|
||||
?: false
|
||||
val poster = filmPoster.selectFirst("> img")!!.attr("data-src")
|
||||
val set: EnumSet<DubStatus> =
|
||||
EnumSet.of(if (isDub) DubStatus.Dubbed else DubStatus.Subbed)
|
||||
AnimeSearchResponse(title, href, this.name, TvType.Anime, poster, null, set)
|
||||
}
|
||||
items.add(HomePageList(i.second, results))
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
if (items.size <= 0) throw ErrorLoadingException()
|
||||
return HomePageResponse(items)
|
||||
}
|
||||
|
||||
|
||||
private fun fixAnimeLink(url: String): String {
|
||||
val regex = "watch/([a-zA-Z\\-0-9]*)-episode".toRegex()
|
||||
val (aniId) = regex.find(url)!!.destructured
|
||||
return "$mainUrl/anime/$aniId"
|
||||
}
|
||||
|
||||
private fun parseSearchPage(soup: Document): List<SearchResponse> {
|
||||
val items = soup.select(".film_list-wrap > .flw-item")
|
||||
if (items.isEmpty()) return ArrayList()
|
||||
return items.map { i ->
|
||||
val href = fixAnimeLink(i.selectFirst("a")!!.attr("href"))
|
||||
val img = fixUrl(i.selectFirst("img")!!.attr("data-src"))
|
||||
val title = i.selectFirst("img")!!.attr("title")
|
||||
val isDub = !i.select(".pick.film-poster-quality").isEmpty()
|
||||
val year =
|
||||
i.selectFirst(".film-detail.film-detail-fix > div > span:nth-child(1)")!!.text()
|
||||
.toIntOrNull()
|
||||
val type =
|
||||
i.selectFirst(".film-detail.film-detail-fix > div > span:nth-child(3)")!!.text()
|
||||
|
||||
if (getType(type) == TvType.AnimeMovie) {
|
||||
MovieSearchResponse(
|
||||
title, href, this.name, TvType.AnimeMovie, img, year
|
||||
)
|
||||
} else {
|
||||
AnimeSearchResponse(
|
||||
title,
|
||||
href,
|
||||
this.name,
|
||||
TvType.Anime,
|
||||
img,
|
||||
year,
|
||||
EnumSet.of(if (isDub) DubStatus.Dubbed else DubStatus.Subbed),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
val url = "$mainUrl/search"
|
||||
val response =
|
||||
app.get(url, params = mapOf("keyword" to query))
|
||||
var document = Jsoup.parse(response.text)
|
||||
val returnValue = parseSearchPage(document).toMutableList()
|
||||
|
||||
while (!document.select(".pagination").isEmpty()) {
|
||||
val link = document.select("a.page-link[rel=\"next\"]")
|
||||
if (!link.isEmpty() && returnValue.size < 40) {
|
||||
val extraResponse = app.get(fixUrl(link[0].attr("href"))).text
|
||||
document = Jsoup.parse(extraResponse)
|
||||
returnValue.addAll(parseSearchPage(document))
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return returnValue.distinctBy { it.url }
|
||||
}
|
||||
|
||||
override suspend fun quickSearch(query: String): List<SearchResponse> {
|
||||
val response = JSONObject(
|
||||
app.post(
|
||||
"https://wcostream.cc/ajax/search",
|
||||
data = mapOf("keyword" to query)
|
||||
).text
|
||||
).getString("html") // I won't make a dataclass for this shit
|
||||
val document = Jsoup.parse(response)
|
||||
|
||||
return document.select("a.nav-item").mapNotNull {
|
||||
val title = it.selectFirst("img")?.attr("title") ?: return@mapNotNull null
|
||||
val img = it?.selectFirst("img")?.attr("src") ?: return@mapNotNull null
|
||||
val href = it?.attr("href") ?: return@mapNotNull null
|
||||
val isDub = title.contains("(Dub)")
|
||||
val filmInfo = it.selectFirst(".film-infor")
|
||||
val year = filmInfo?.select("span")?.get(0)?.text()?.toIntOrNull()
|
||||
val type = filmInfo?.select("span")?.get(1)?.text().toString()
|
||||
if (getType(type) == TvType.AnimeMovie) {
|
||||
MovieSearchResponse(
|
||||
title, href, this.name, TvType.AnimeMovie, img, year
|
||||
)
|
||||
} else {
|
||||
AnimeSearchResponse(
|
||||
title,
|
||||
href,
|
||||
this.name,
|
||||
TvType.Anime,
|
||||
img,
|
||||
year,
|
||||
EnumSet.of(if (isDub) DubStatus.Dubbed else DubStatus.Subbed),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val response = app.get(url, timeout = 120).text
|
||||
val document = Jsoup.parse(response)
|
||||
|
||||
val japaneseTitle =
|
||||
document.selectFirst("div.elements div.row > div:nth-child(1) > div.row-line:nth-child(1)")
|
||||
?.text()?.trim()?.replace("Other names:", "")?.trim()
|
||||
|
||||
val canonicalTitle = document.selectFirst("meta[name=\"title\"]")
|
||||
?.attr("content")?.split("| W")?.get(0).toString()
|
||||
|
||||
val isDubbed = canonicalTitle.contains("Dub")
|
||||
val episodeNodes = document.select(".tab-content .nav-item > a")
|
||||
|
||||
val episodes = ArrayList(episodeNodes?.map {
|
||||
Episode(it.attr("href"))
|
||||
} ?: ArrayList())
|
||||
|
||||
val statusElem =
|
||||
document.selectFirst("div.elements div.row > div:nth-child(1) > div.row-line:nth-child(2)")
|
||||
val status = when (statusElem?.text()?.replace("Status:", "")?.trim()) {
|
||||
"Ongoing" -> ShowStatus.Ongoing
|
||||
"Completed" -> ShowStatus.Completed
|
||||
else -> null
|
||||
}
|
||||
val yearText =
|
||||
document.selectFirst("div.elements div.row > div:nth-child(2) > div.row-line:nth-child(4)")
|
||||
?.text()
|
||||
val year = yearText?.replace("Date release:", "")?.trim()?.split("-")?.get(0)?.toIntOrNull()
|
||||
|
||||
val poster = document.selectFirst(".film-poster-img")?.attr("src")
|
||||
val type = document.selectFirst("span.item.mr-1 > a")?.text()?.trim()
|
||||
|
||||
val synopsis = document.selectFirst(".description > p")?.text()?.trim()
|
||||
val genre =
|
||||
document.select("div.elements div.row > div:nth-child(1) > div.row-line:nth-child(5) > a")
|
||||
.map { it?.text()?.trim().toString() }
|
||||
|
||||
return newAnimeLoadResponse(canonicalTitle, url, getType(type ?: "")) {
|
||||
japName = japaneseTitle
|
||||
engName = canonicalTitle
|
||||
posterUrl = poster
|
||||
this.year = year
|
||||
addEpisodes(if (isDubbed) DubStatus.Dubbed else DubStatus.Subbed, episodes)
|
||||
showStatus = status
|
||||
plot = synopsis
|
||||
tags = genre
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
val response = app.get(data).text
|
||||
val servers = Jsoup.parse(response).select("#servers-list > ul > li").map {
|
||||
mapOf(
|
||||
"link" to it?.selectFirst("a")?.attr("data-embed"),
|
||||
"title" to it?.selectFirst("span")?.text()?.trim()
|
||||
)
|
||||
}
|
||||
|
||||
for (server in servers) {
|
||||
WcoStream().getSafeUrl(server["link"].toString(), null, subtitleCallback, callback)
|
||||
Mcloud().getSafeUrl(server["link"].toString(), null, subtitleCallback, callback)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -1,172 +0,0 @@
|
|||
package com.lagradost.cloudstream3.animeproviders
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Element
|
||||
|
||||
class WcofunProvider : MainAPI() {
|
||||
override var mainUrl = "https://www.wcofun.com"
|
||||
override var name = "WCO Fun"
|
||||
override val hasMainPage = true
|
||||
override val hasDownloadSupport = true
|
||||
|
||||
override val supportedTypes = setOf(
|
||||
TvType.Anime,
|
||||
TvType.AnimeMovie,
|
||||
TvType.OVA
|
||||
)
|
||||
|
||||
override suspend fun getMainPage(
|
||||
page: Int,
|
||||
request: MainPageRequest
|
||||
): HomePageResponse {
|
||||
val document = app.get(mainUrl).document
|
||||
|
||||
val homePageList = ArrayList<HomePageList>()
|
||||
|
||||
document.select("div#sidebar_right,div#sidebar_right2").forEach { block ->
|
||||
val header = block.previousElementSibling()?.ownText() ?: return@forEach
|
||||
val animes = block.select("ul.items li").mapNotNull {
|
||||
it.toSearchResult()
|
||||
}
|
||||
if (animes.isNotEmpty()) homePageList.add(HomePageList(header, animes))
|
||||
}
|
||||
|
||||
return HomePageResponse(homePageList)
|
||||
|
||||
}
|
||||
|
||||
private fun getProperAnimeLink(uri: String): String {
|
||||
return if (uri.contains("/anime/")) {
|
||||
uri
|
||||
} else {
|
||||
var title = uri.substringAfter("$mainUrl/")
|
||||
title = when {
|
||||
(title.contains(Regex("-season-[0-9]+-episode"))) && title.contains("-dubbed") -> title.substringBefore("-season")
|
||||
(title.contains(Regex("-season-[0-9]+-episode"))) && title.contains("-subbed") -> title.replace(Regex("-season-[0-9]+-episode-[0-9]+"), "")
|
||||
title.contains("-subbed") -> title.replace(Regex("-episode-[0-9]+"), "")
|
||||
title.contains("-dubbed") -> title.substringBefore("-episode")
|
||||
else -> title
|
||||
}
|
||||
"$mainUrl/anime/$title"
|
||||
}
|
||||
}
|
||||
|
||||
private fun Element.toSearchResult(): AnimeSearchResponse? {
|
||||
val header = this.selectFirst("div.recent-release-episodes a")?.text()
|
||||
val title = header?.trim() ?: return null
|
||||
val href = getProperAnimeLink(this.selectFirst("a")!!.attr("href"))
|
||||
val posterUrl = fixUrlNull(this.selectFirst("img")?.attr("src"))
|
||||
val epNum = header.let { eps ->
|
||||
Regex("Episode\\s?([0-9]+)").find(eps)?.groupValues?.getOrNull(1)?.toIntOrNull()
|
||||
}
|
||||
val isDub = header.contains("Dubbed")
|
||||
val isSub = header.contains("Subbed")
|
||||
return newAnimeSearchResponse(title, href, TvType.Anime) {
|
||||
this.posterUrl = posterUrl
|
||||
addDubStatus(isDub, isSub, epNum, epNum)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
val document = app.post(
|
||||
"$mainUrl/search",
|
||||
referer = mainUrl,
|
||||
data = mapOf("catara" to query, "konuara" to "series")
|
||||
).document
|
||||
|
||||
return document.select("div#sidebar_right2 li").mapNotNull {
|
||||
it.toSearchResult()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse? {
|
||||
val document = app.get(url).document
|
||||
val title = document.selectFirst("div.h1-tag a")?.text() ?: return null
|
||||
val eps = document.select("div#sidebar_right3 div.cat-eps")
|
||||
val type = if (eps.size == 1 || eps.first()?.text()
|
||||
?.contains(Regex("Episode\\s?[0-9]+")) != true
|
||||
) TvType.AnimeMovie else TvType.Anime
|
||||
val episodes = eps.map {
|
||||
val name = it.select("a").text()
|
||||
val link = it.selectFirst("a")!!.attr("href")
|
||||
Episode(link, name = name)
|
||||
}.reversed()
|
||||
|
||||
return newAnimeLoadResponse(title, url, type) {
|
||||
posterUrl = fixUrlNull(document.selectFirst("img.img5")?.attr("src"))
|
||||
addEpisodes(DubStatus.Subbed, episodes)
|
||||
plot = document.select("div#sidebar_cat > p").text()
|
||||
this.tags = document.select("div#sidebar_cat a").map { it.text() }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getIframe(url: String): String? {
|
||||
val document = app.get(url).document
|
||||
val scriptData =
|
||||
document.select("script").find { it.data().contains("= \"\";") }?.data() ?: return null
|
||||
val subtractionNumber =
|
||||
Regex("""(?<=\.replace\(/\\D/g,''\)\) - )\d+""").find(scriptData)?.value?.toInt()
|
||||
?: return null
|
||||
val html = Regex("""(?<=\["|, ").+?(?=")""").findAll(scriptData).map {
|
||||
val number = base64Decode(it.value).replace(Regex("\\D"), "").toInt()
|
||||
(number - subtractionNumber).toChar()
|
||||
}.joinToString("")
|
||||
return Jsoup.parse(html).select("iframe").attr("src").let { fixUrl(it) }
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
|
||||
getIframe(data)?.let { iframe ->
|
||||
val link = app.get(iframe, referer = data).text.let {
|
||||
fixUrlNull(
|
||||
Regex("\"(/inc/embed/getvidlink.php.*)\"").find(it)?.groupValues?.getOrNull(
|
||||
1
|
||||
)
|
||||
)
|
||||
}
|
||||
app.get(
|
||||
link ?: return@let,
|
||||
referer = iframe,
|
||||
headers = mapOf("x-requested-with" to "XMLHttpRequest")
|
||||
).parsedSafe<Sources>()?.let {
|
||||
listOf(
|
||||
Pair(it.hd, "HD"),
|
||||
Pair(it.enc, "SD")
|
||||
).map { source ->
|
||||
suspendSafeApiCall {
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
"${this.name} ${source.second}",
|
||||
"${this.name} ${source.second}",
|
||||
"${it.server}/getvid?evid=${source.first}",
|
||||
mainUrl,
|
||||
if (source.second == "HD") Qualities.P720.value else Qualities.P480.value
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
data class Sources(
|
||||
@JsonProperty("enc") val enc: String?,
|
||||
@JsonProperty("server") val server: String?,
|
||||
@JsonProperty("cdn") val cdn: String?,
|
||||
@JsonProperty("hd") val hd: String?,
|
||||
)
|
||||
|
||||
|
||||
}
|
|
@ -1,368 +0,0 @@
|
|||
package com.lagradost.cloudstream3.animeproviders
|
||||
|
||||
import android.util.Log
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addAniListId
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addMalId
|
||||
import com.lagradost.cloudstream3.movieproviders.SflixProvider.Companion.extractRabbitStream
|
||||
import com.lagradost.cloudstream3.movieproviders.SflixProvider.Companion.runSflixExtractorVerifierJob
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
import com.lagradost.nicehttp.Requests.Companion.await
|
||||
import okhttp3.Interceptor
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Element
|
||||
import java.net.URI
|
||||
|
||||
private const val OPTIONS = "OPTIONS"
|
||||
|
||||
class ZoroProvider : MainAPI() {
|
||||
override var mainUrl = "https://zoro.to"
|
||||
override var name = "Zoro"
|
||||
override val hasQuickSearch = false
|
||||
override val hasMainPage = true
|
||||
override val hasChromecastSupport = true
|
||||
override val hasDownloadSupport = true
|
||||
override val usesWebView = true
|
||||
|
||||
override val supportedTypes = setOf(
|
||||
TvType.Anime,
|
||||
TvType.AnimeMovie,
|
||||
TvType.OVA
|
||||
)
|
||||
|
||||
companion object {
|
||||
fun getType(t: String): TvType {
|
||||
return if (t.contains("OVA") || t.contains("Special")) TvType.OVA
|
||||
else if (t.contains("Movie")) TvType.AnimeMovie
|
||||
else TvType.Anime
|
||||
}
|
||||
|
||||
fun getStatus(t: String): ShowStatus {
|
||||
return when (t) {
|
||||
"Finished Airing" -> ShowStatus.Completed
|
||||
"Currently Airing" -> ShowStatus.Ongoing
|
||||
else -> ShowStatus.Completed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val epRegex = Regex("Ep (\\d+)/")
|
||||
private fun Element.toSearchResult(): SearchResponse? {
|
||||
val href = fixUrl(this.select("a").attr("href"))
|
||||
val title = this.select("h3.film-name").text()
|
||||
val dubSub = this.select(".film-poster > .tick.ltr").text()
|
||||
//val episodes = this.selectFirst(".film-poster > .tick-eps")?.text()?.toIntOrNull()
|
||||
|
||||
val dubExist = dubSub.contains("dub", ignoreCase = true)
|
||||
val subExist = dubSub.contains("sub", ignoreCase = true)
|
||||
val episodes =
|
||||
this.selectFirst(".film-poster > .tick.rtl > .tick-eps")?.text()?.let { eps ->
|
||||
//println("REGEX:::: $eps")
|
||||
// current episode / max episode
|
||||
//Regex("Ep (\\d+)/(\\d+)")
|
||||
epRegex.find(eps)?.groupValues?.get(1)?.toIntOrNull()
|
||||
}
|
||||
if (href.contains("/news/") || title.trim().equals("News", ignoreCase = true)) return null
|
||||
val posterUrl = fixUrl(this.select("img").attr("data-src"))
|
||||
val type = getType(this.select("div.fd-infor > span.fdi-item").text())
|
||||
|
||||
return newAnimeSearchResponse(title, href, type) {
|
||||
this.posterUrl = posterUrl
|
||||
addDubStatus(dubExist, subExist, episodes, episodes)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse {
|
||||
val html = app.get("$mainUrl/home").text
|
||||
val document = Jsoup.parse(html)
|
||||
|
||||
val homePageList = ArrayList<HomePageList>()
|
||||
|
||||
document.select("div.anif-block").forEach { block ->
|
||||
val header = block.select("div.anif-block-header").text().trim()
|
||||
val animes = block.select("li").mapNotNull {
|
||||
it.toSearchResult()
|
||||
}
|
||||
if (animes.isNotEmpty()) homePageList.add(HomePageList(header, animes))
|
||||
}
|
||||
|
||||
document.select("section.block_area.block_area_home").forEach { block ->
|
||||
val header = block.select("h2.cat-heading").text().trim()
|
||||
val animes = block.select("div.flw-item").mapNotNull {
|
||||
it.toSearchResult()
|
||||
}
|
||||
if (animes.isNotEmpty()) homePageList.add(HomePageList(header, animes))
|
||||
}
|
||||
|
||||
return HomePageResponse(homePageList)
|
||||
}
|
||||
|
||||
private data class Response(
|
||||
@JsonProperty("status") val status: Boolean,
|
||||
@JsonProperty("html") val html: String
|
||||
)
|
||||
|
||||
// override suspend fun quickSearch(query: String): List<SearchResponse> {
|
||||
// val url = "$mainUrl/ajax/search/suggest?keyword=${query}"
|
||||
// val html = mapper.readValue<Response>(khttp.get(url).text).html
|
||||
// val document = Jsoup.parse(html)
|
||||
//
|
||||
// return document.select("a.nav-item").map {
|
||||
// val title = it.selectFirst(".film-name")?.text().toString()
|
||||
// val href = fixUrl(it.attr("href"))
|
||||
// val year = it.selectFirst(".film-infor > span")?.text()?.split(",")?.get(1)?.trim()?.toIntOrNull()
|
||||
// val image = it.select("img").attr("data-src")
|
||||
//
|
||||
// AnimeSearchResponse(
|
||||
// title,
|
||||
// href,
|
||||
// this.name,
|
||||
// TvType.TvSeries,
|
||||
// image,
|
||||
// year,
|
||||
// null,
|
||||
// EnumSet.of(DubStatus.Subbed),
|
||||
// null,
|
||||
// null
|
||||
// )
|
||||
//
|
||||
// }
|
||||
// }
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
val link = "$mainUrl/search?keyword=$query"
|
||||
val html = app.get(link).text
|
||||
val document = Jsoup.parse(html)
|
||||
|
||||
return document.select(".flw-item").map {
|
||||
val title = it.selectFirst(".film-detail > .film-name > a")?.attr("title").toString()
|
||||
val filmPoster = it.selectFirst(".film-poster")
|
||||
val poster = filmPoster!!.selectFirst("img")?.attr("data-src")
|
||||
|
||||
val episodes = filmPoster.selectFirst("div.rtl > div.tick-eps")?.text()?.let { eps ->
|
||||
// current episode / max episode
|
||||
val epRegex = Regex("Ep (\\d+)/")//Regex("Ep (\\d+)/(\\d+)")
|
||||
epRegex.find(eps)?.groupValues?.get(1)?.toIntOrNull()
|
||||
}
|
||||
val dubsub = filmPoster.selectFirst("div.ltr")?.text()
|
||||
val dubExist = dubsub?.contains("DUB") ?: false
|
||||
val subExist = dubsub?.contains("SUB") ?: false || dubsub?.contains("RAW") ?: false
|
||||
|
||||
val tvType =
|
||||
getType(it.selectFirst(".film-detail > .fd-infor > .fdi-item")?.text().toString())
|
||||
val href = fixUrl(it.selectFirst(".film-name a")!!.attr("href"))
|
||||
|
||||
newAnimeSearchResponse(title, href, tvType) {
|
||||
this.posterUrl = poster
|
||||
addDubStatus(dubExist, subExist, episodes, episodes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Element?.getActor(): Actor? {
|
||||
val image =
|
||||
fixUrlNull(this?.selectFirst(".pi-avatar > img")?.attr("data-src")) ?: return null
|
||||
val name = this?.selectFirst(".pi-detail > .pi-name")?.text() ?: return null
|
||||
return Actor(name = name, image = image)
|
||||
}
|
||||
|
||||
data class ZoroSyncData(
|
||||
@JsonProperty("mal_id") val malId: String?,
|
||||
@JsonProperty("anilist_id") val aniListId: String?,
|
||||
)
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val html = app.get(url).text
|
||||
val document = Jsoup.parse(html)
|
||||
|
||||
val syncData = tryParseJson<ZoroSyncData>(document.selectFirst("#syncData")?.data())
|
||||
|
||||
val title = document.selectFirst(".anisc-detail > .film-name")?.text().toString()
|
||||
val poster = document.selectFirst(".anisc-poster img")?.attr("src")
|
||||
val tags = document.select(".anisc-info a[href*=\"/genre/\"]").map { it.text() }
|
||||
|
||||
var year: Int? = null
|
||||
var japaneseTitle: String? = null
|
||||
var status: ShowStatus? = null
|
||||
|
||||
for (info in document.select(".anisc-info > .item.item-title")) {
|
||||
val text = info?.text().toString()
|
||||
when {
|
||||
(year != null && japaneseTitle != null && status != null) -> break
|
||||
text.contains("Premiered") && year == null ->
|
||||
year =
|
||||
info.selectFirst(".name")?.text().toString().split(" ").last().toIntOrNull()
|
||||
|
||||
text.contains("Japanese") && japaneseTitle == null ->
|
||||
japaneseTitle = info.selectFirst(".name")?.text().toString()
|
||||
|
||||
text.contains("Status") && status == null ->
|
||||
status = getStatus(info.selectFirst(".name")?.text().toString())
|
||||
}
|
||||
}
|
||||
|
||||
val description = document.selectFirst(".film-description.m-hide > .text")?.text()
|
||||
val animeId = URI(url).path.split("-").last()
|
||||
|
||||
val episodes = Jsoup.parse(
|
||||
parseJson<Response>(
|
||||
app.get(
|
||||
"$mainUrl/ajax/v2/episode/list/$animeId"
|
||||
).text
|
||||
).html
|
||||
).select(".ss-list > a[href].ssl-item.ep-item").map {
|
||||
newEpisode(it.attr("href")) {
|
||||
this.name = it?.attr("title")
|
||||
this.episode = it.selectFirst(".ssli-order")?.text()?.toIntOrNull()
|
||||
}
|
||||
}
|
||||
|
||||
val actors = document.select("div.block-actors-content > div.bac-list-wrap > div.bac-item")
|
||||
.mapNotNull { head ->
|
||||
val subItems = head.select(".per-info") ?: return@mapNotNull null
|
||||
if (subItems.isEmpty()) return@mapNotNull null
|
||||
var role: ActorRole? = null
|
||||
val mainActor = subItems.first()?.let {
|
||||
role = when (it.selectFirst(".pi-detail > .pi-cast")?.text()?.trim()) {
|
||||
"Supporting" -> ActorRole.Supporting
|
||||
"Main" -> ActorRole.Main
|
||||
else -> null
|
||||
}
|
||||
it.getActor()
|
||||
} ?: return@mapNotNull null
|
||||
val voiceActor = if (subItems.size >= 2) subItems[1]?.getActor() else null
|
||||
ActorData(actor = mainActor, role = role, voiceActor = voiceActor)
|
||||
}
|
||||
|
||||
val recommendations =
|
||||
document.select("#main-content > section > .tab-content > div > .film_list-wrap > .flw-item")
|
||||
.mapNotNull { head ->
|
||||
val filmPoster = head?.selectFirst(".film-poster")
|
||||
val epPoster = filmPoster?.selectFirst("img")?.attr("data-src")
|
||||
val a = head?.selectFirst(".film-detail > .film-name > a")
|
||||
val epHref = a?.attr("href")
|
||||
val epTitle = a?.attr("title")
|
||||
if (epHref == null || epTitle == null || epPoster == null) {
|
||||
null
|
||||
} else {
|
||||
AnimeSearchResponse(
|
||||
epTitle,
|
||||
fixUrl(epHref),
|
||||
this.name,
|
||||
TvType.Anime,
|
||||
epPoster,
|
||||
dubStatus = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return newAnimeLoadResponse(title, url, TvType.Anime) {
|
||||
japName = japaneseTitle
|
||||
engName = title
|
||||
posterUrl = poster
|
||||
this.year = year
|
||||
addEpisodes(DubStatus.Subbed, episodes)
|
||||
showStatus = status
|
||||
plot = description
|
||||
this.tags = tags
|
||||
this.recommendations = recommendations
|
||||
this.actors = actors
|
||||
addMalId(syncData?.malId?.toIntOrNull())
|
||||
addAniListId(syncData?.aniListId?.toIntOrNull())
|
||||
}
|
||||
}
|
||||
|
||||
private data class RapidCloudResponse(
|
||||
@JsonProperty("link") val link: String
|
||||
)
|
||||
|
||||
override suspend fun extractorVerifierJob(extractorData: String?) {
|
||||
Log.d(this.name, "Starting ${this.name} job!")
|
||||
runSflixExtractorVerifierJob(this, extractorData, "https://rapid-cloud.ru/")
|
||||
}
|
||||
|
||||
/** Url hashcode to sid */
|
||||
var sid: HashMap<Int, String?> = hashMapOf()
|
||||
|
||||
/**
|
||||
* Makes an identical Options request before .ts request
|
||||
* Adds an SID header to the .ts request.
|
||||
* */
|
||||
override fun getVideoInterceptor(extractorLink: ExtractorLink): Interceptor {
|
||||
return Interceptor { chain ->
|
||||
val request = chain.request()
|
||||
if (request.url.toString().endsWith(".ts")
|
||||
&& request.method != OPTIONS
|
||||
// No option requests on VidCloud
|
||||
&& !request.url.toString().contains("betterstream")
|
||||
) {
|
||||
val newRequest =
|
||||
chain.request()
|
||||
.newBuilder().apply {
|
||||
sid[extractorLink.url.hashCode()]?.let { sid ->
|
||||
addHeader("SID", sid)
|
||||
}
|
||||
}
|
||||
.build()
|
||||
val options = request.newBuilder().method(OPTIONS, request.body).build()
|
||||
ioSafe { app.baseClient.newCall(options).await() }
|
||||
|
||||
return@Interceptor chain.proceed(newRequest)
|
||||
} else {
|
||||
return@Interceptor chain.proceed(chain.request())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
|
||||
val servers: List<Pair<DubStatus, String>> = Jsoup.parse(
|
||||
app.get("$mainUrl/ajax/v2/episode/servers?episodeId=" + data.split("=")[1])
|
||||
.parsed<Response>().html
|
||||
).select(".server-item[data-type][data-id]").map {
|
||||
Pair(
|
||||
if (it.attr("data-type") == "sub") DubStatus.Subbed else DubStatus.Dubbed,
|
||||
it.attr("data-id")
|
||||
)
|
||||
}
|
||||
|
||||
val extractorData =
|
||||
"https://ws1.rapid-cloud.ru/socket.io/?EIO=4&transport=polling"
|
||||
|
||||
// Prevent duplicates
|
||||
servers.distinctBy { it.second }.apmap {
|
||||
val link =
|
||||
"$mainUrl/ajax/v2/episode/sources?id=${it.second}"
|
||||
val extractorLink = app.get(
|
||||
link,
|
||||
).parsed<RapidCloudResponse>().link
|
||||
val hasLoadedExtractorLink =
|
||||
loadExtractor(extractorLink, "https://rapid-cloud.ru/", subtitleCallback, callback)
|
||||
|
||||
if (!hasLoadedExtractorLink) {
|
||||
extractRabbitStream(
|
||||
extractorLink,
|
||||
subtitleCallback,
|
||||
// Blacklist VidCloud for now
|
||||
{ videoLink -> if (!videoLink.url.contains("betterstream")) callback(videoLink) },
|
||||
true,
|
||||
extractorData
|
||||
) { sourceName ->
|
||||
sourceName + " - ${it.first}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.base64Decode
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
|
||||
class Acefile : ExtractorApi() {
|
||||
override val name = "Acefile"
|
||||
override val mainUrl = "https://acefile.co"
|
||||
override val requiresReferer = false
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> {
|
||||
val sources = mutableListOf<ExtractorLink>()
|
||||
app.get(url).document.select("script").map { script ->
|
||||
if (script.data().contains("eval(function(p,a,c,k,e,d)")) {
|
||||
val data = getAndUnpack(script.data())
|
||||
val id = data.substringAfter("{\"id\":\"").substringBefore("\",")
|
||||
val key = data.substringAfter("var nfck=\"").substringBefore("\";")
|
||||
app.get("https://acefile.co/local/$id?key=$key").text.let {
|
||||
base64Decode(
|
||||
it.substringAfter("JSON.parse(atob(\"").substringBefore("\"))")
|
||||
).let { res ->
|
||||
sources.add(
|
||||
ExtractorLink(
|
||||
name,
|
||||
name,
|
||||
res.substringAfter("\"file\":\"").substringBefore("\","),
|
||||
"$mainUrl/",
|
||||
Qualities.Unknown.value,
|
||||
headers = mapOf("range" to "bytes=0-")
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return sources
|
||||
}
|
||||
|
||||
}
|
|
@ -1,46 +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.M3u8Helper
|
||||
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||
import java.net.URI
|
||||
|
||||
class AsianLoad : ExtractorApi() {
|
||||
override var name = "AsianLoad"
|
||||
override var mainUrl = "https://asianembed.io"
|
||||
override val requiresReferer = true
|
||||
|
||||
private val sourceRegex = Regex("""sources:[\W\w]*?file:\s*?["'](.*?)["']""")
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> {
|
||||
val extractedLinksList: MutableList<ExtractorLink> = mutableListOf()
|
||||
with(app.get(url, referer = referer)) {
|
||||
sourceRegex.findAll(this.text).forEach { sourceMatch ->
|
||||
val extractedUrl = sourceMatch.groupValues[1]
|
||||
// Trusting this isn't mp4, may fuck up stuff
|
||||
if (URI(extractedUrl).path.endsWith(".m3u8")) {
|
||||
M3u8Helper.generateM3u8(
|
||||
name,
|
||||
extractedUrl,
|
||||
url,
|
||||
headers = mapOf("referer" to this.url)
|
||||
).forEach { link ->
|
||||
extractedLinksList.add(link)
|
||||
}
|
||||
} else if (extractedUrl.endsWith(".mp4")) {
|
||||
extractedLinksList.add(
|
||||
ExtractorLink(
|
||||
name,
|
||||
name,
|
||||
extractedUrl,
|
||||
url.replace(" ", "%20"),
|
||||
getQualityFromName(sourceMatch.groupValues[2]),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
return extractedLinksList
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
|
||||
|
||||
class Blogger : ExtractorApi() {
|
||||
override val name = "Blogger"
|
||||
override val mainUrl = "https://www.blogger.com"
|
||||
override val requiresReferer = false
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> {
|
||||
val sources = mutableListOf<ExtractorLink>()
|
||||
with(app.get(url).document) {
|
||||
this.select("script").map { script ->
|
||||
if (script.data().contains("\"streams\":[")) {
|
||||
val data = script.data().substringAfter("\"streams\":[")
|
||||
.substringBefore("]")
|
||||
tryParseJson<List<ResponseSource>>("[$data]")?.map {
|
||||
sources.add(
|
||||
ExtractorLink(
|
||||
name,
|
||||
name,
|
||||
it.play_url,
|
||||
referer = "https://www.youtube.com/",
|
||||
quality = when (it.format_id) {
|
||||
18 -> 360
|
||||
22 -> 720
|
||||
else -> Qualities.Unknown.value
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return sources
|
||||
}
|
||||
|
||||
private data class ResponseSource(
|
||||
@JsonProperty("play_url") val play_url: String,
|
||||
@JsonProperty("format_id") val format_id: Int
|
||||
)
|
||||
}
|
|
@ -1,29 +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.M3u8Helper
|
||||
|
||||
class BullStream : ExtractorApi() {
|
||||
override val name = "BullStream"
|
||||
override val mainUrl = "https://bullstream.xyz"
|
||||
override val requiresReferer = false
|
||||
val regex = Regex("(?<=sniff\\()(.*)(?=\\)\\);)")
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
|
||||
val data = regex.find(app.get(url).text)?.value
|
||||
?.replace("\"", "")
|
||||
?.split(",")
|
||||
?: return null
|
||||
|
||||
val m3u8 = "$mainUrl/m3u8/${data[1]}/${data[2]}/master.txt?s=1&cache=${data[4]}"
|
||||
println("shiv : $m3u8")
|
||||
return M3u8Helper.generateM3u8(
|
||||
name,
|
||||
m3u8,
|
||||
url,
|
||||
headers = mapOf("referer" to url, "accept" to "*/*")
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,64 +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.getQualityFromName
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
class DoodCxExtractor : DoodLaExtractor() {
|
||||
override var mainUrl = "https://dood.cx"
|
||||
}
|
||||
|
||||
class DoodShExtractor : DoodLaExtractor() {
|
||||
override var mainUrl = "https://dood.sh"
|
||||
}
|
||||
class DoodWatchExtractor : DoodLaExtractor() {
|
||||
override var mainUrl = "https://dood.watch"
|
||||
}
|
||||
|
||||
class DoodPmExtractor : DoodLaExtractor() {
|
||||
override var mainUrl = "https://dood.pm"
|
||||
}
|
||||
|
||||
class DoodToExtractor : DoodLaExtractor() {
|
||||
override var mainUrl = "https://dood.to"
|
||||
}
|
||||
|
||||
class DoodSoExtractor : DoodLaExtractor() {
|
||||
override var mainUrl = "https://dood.so"
|
||||
}
|
||||
|
||||
class DoodWsExtractor : DoodLaExtractor() {
|
||||
override var mainUrl = "https://dood.ws"
|
||||
}
|
||||
|
||||
|
||||
open class DoodLaExtractor : ExtractorApi() {
|
||||
override var name = "DoodStream"
|
||||
override var mainUrl = "https://dood.la"
|
||||
override val requiresReferer = false
|
||||
|
||||
override fun getExtractorUrl(id: String): String {
|
||||
return "$mainUrl/d/$id"
|
||||
}
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
|
||||
val response0 = app.get(url).text // html of DoodStream page to look for /pass_md5/...
|
||||
val md5 =mainUrl+(Regex("/pass_md5/[^']*").find(response0)?.value ?: return null) // get https://dood.ws/pass_md5/...
|
||||
val trueUrl = app.get(md5, referer = url).text + "zUEJeL3mUN?token=" + md5.substringAfterLast("/") //direct link to extract (zUEJeL3mUN is random)
|
||||
val quality = Regex("\\d{3,4}p").find(response0.substringAfter("<title>").substringBefore("</title>"))?.groupValues?.get(0)
|
||||
return listOf(
|
||||
ExtractorLink(
|
||||
trueUrl,
|
||||
this.name,
|
||||
trueUrl,
|
||||
mainUrl,
|
||||
getQualityFromName(quality),
|
||||
false
|
||||
)
|
||||
) // links are valid in 8h
|
||||
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
import com.lagradost.cloudstream3.app
|
||||
|
||||
class Evoload1 : Evoload() {
|
||||
override var mainUrl = "https://evoload.io"
|
||||
}
|
||||
|
||||
open class Evoload : ExtractorApi() {
|
||||
override val name: String = "Evoload"
|
||||
override val mainUrl: String = "https://www.evoload.io"
|
||||
//private val srcRegex = Regex("""video .*src="(.*)""""") // would be possible to use the parse and find src attribute
|
||||
override val requiresReferer = true
|
||||
|
||||
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> {
|
||||
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)
|
||||
val r = app.post("https://evoload.io/SecurePlayer", data=(payload)).text
|
||||
val link = Regex("src\":\"(.*?)\"").find(r)?.destructured?.component1() ?: return listOf()
|
||||
return listOf(
|
||||
ExtractorLink(
|
||||
name,
|
||||
name + flag,
|
||||
link,
|
||||
cleaned_url,
|
||||
Qualities.Unknown.value,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
|
||||
|
||||
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?): List<ExtractorLink> {
|
||||
val sources = mutableListOf<ExtractorLink>()
|
||||
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<List<ResponseSource>>("[$data]")?.map {
|
||||
M3u8Helper.generateM3u8(
|
||||
name,
|
||||
it.file,
|
||||
"$mainUrl/",
|
||||
).forEach { m3uData -> sources.add(m3uData) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return sources
|
||||
}
|
||||
|
||||
private data class ResponseSource(
|
||||
@JsonProperty("file") val file: String,
|
||||
@JsonProperty("type") val type: String?,
|
||||
@JsonProperty("label") val label: String?
|
||||
)
|
||||
|
||||
}
|
|
@ -1,39 +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.M3u8Helper
|
||||
|
||||
class GMPlayer : ExtractorApi() {
|
||||
override val name = "GM Player"
|
||||
override val mainUrl = "https://gmplayer.xyz"
|
||||
override val requiresReferer = true
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
|
||||
val ref = referer ?: return null
|
||||
val id = url.substringAfter("/video/").substringBefore("/")
|
||||
|
||||
val m3u8 = app.post(
|
||||
"$mainUrl/player/index.php?data=$id&do=getVideo",
|
||||
mapOf(
|
||||
"accept" to "*/*",
|
||||
"referer" to ref,
|
||||
"x-requested-with" to "XMLHttpRequest",
|
||||
"origin" to mainUrl
|
||||
),
|
||||
data = mapOf("hash" to id, "r" to ref)
|
||||
).parsed<GmResponse>().videoSource ?: return null
|
||||
|
||||
return M3u8Helper.generateM3u8(
|
||||
name,
|
||||
m3u8,
|
||||
ref,
|
||||
headers = mapOf("accept" to "*/*")
|
||||
)
|
||||
}
|
||||
|
||||
private data class GmResponse(
|
||||
val videoSource: String? = null
|
||||
)
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.network.WebViewResolver
|
||||
import com.lagradost.cloudstream3.utils.ExtractorApi
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.M3u8Helper
|
||||
|
||||
|
||||
open class GenericM3U8 : ExtractorApi() {
|
||||
override var name = "Upstream"
|
||||
override var mainUrl = "https://upstream.to"
|
||||
override val requiresReferer = false
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> {
|
||||
val response = app.get(
|
||||
url, interceptor = WebViewResolver(
|
||||
Regex("""master\.m3u8""")
|
||||
)
|
||||
)
|
||||
val sources = mutableListOf<ExtractorLink>()
|
||||
if (response.url.contains("m3u8"))
|
||||
M3u8Helper.generateM3u8(
|
||||
name,
|
||||
response.url,
|
||||
url,
|
||||
headers = response.headers.toMap()
|
||||
).forEach { link ->
|
||||
sources.add(link)
|
||||
}
|
||||
return sources
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
|
||||
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<GuardareData>,
|
||||
)
|
||||
|
||||
data class GuardareData (
|
||||
@JsonProperty("file") val file : String,
|
||||
@JsonProperty("label") val label : String,
|
||||
@JsonProperty("type") val type : String
|
||||
)
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
|
||||
val response = app.post(url.replace("/v/","/api/source/"), data = mapOf("d" to mainUrl)).text
|
||||
val jsonvideodata = AppUtils.parseJson<GuardareJsonData>(response)
|
||||
return jsonvideodata.data.map {
|
||||
ExtractorLink(
|
||||
it.file+".${it.type}",
|
||||
this.name,
|
||||
it.file+".${it.type}",
|
||||
mainUrl,
|
||||
it.label.filter{ it.isDigit() }.toInt(),
|
||||
false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
|
||||
|
||||
class Neonime7n : Hxfile() {
|
||||
override val name = "Neonime7n"
|
||||
override val mainUrl = "https://7njctn.neonime.watch"
|
||||
override val redirect = false
|
||||
}
|
||||
|
||||
class Neonime8n : Hxfile() {
|
||||
override val name = "Neonime8n"
|
||||
override val mainUrl = "https://8njctn.neonime.net"
|
||||
override val redirect = false
|
||||
}
|
||||
|
||||
class KotakAnimeid : Hxfile() {
|
||||
override val name = "KotakAnimeid"
|
||||
override val mainUrl = "https://kotakanimeid.com"
|
||||
override val requiresReferer = true
|
||||
}
|
||||
|
||||
class Yufiles : Hxfile() {
|
||||
override val name = "Yufiles"
|
||||
override val mainUrl = "https://yufiles.com"
|
||||
}
|
||||
|
||||
class Aico : Hxfile() {
|
||||
override val name = "Aico"
|
||||
override val mainUrl = "https://aico.pw"
|
||||
}
|
||||
|
||||
open class Hxfile : ExtractorApi() {
|
||||
override val name = "Hxfile"
|
||||
override val mainUrl = "https://hxfile.co"
|
||||
override val requiresReferer = false
|
||||
open val redirect = true
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
|
||||
val sources = mutableListOf<ExtractorLink>()
|
||||
val document = app.get(url, allowRedirects = redirect, referer = referer).document
|
||||
with(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<List<ResponseSource>>("[$data]")?.map {
|
||||
sources.add(
|
||||
ExtractorLink(
|
||||
name,
|
||||
name,
|
||||
it.file,
|
||||
referer = mainUrl,
|
||||
quality = when {
|
||||
url.contains("hxfile.co") -> getQualityFromName(
|
||||
Regex("\\d\\.(.*?).mp4").find(
|
||||
document.select("title").text()
|
||||
)?.groupValues?.get(1).toString()
|
||||
)
|
||||
else -> getQualityFromName(it.label)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
} else if (script.data().contains("\"sources\":[")) {
|
||||
val data = script.data().substringAfter("\"sources\":[").substringBefore("]")
|
||||
tryParseJson<List<ResponseSource>>("[$data]")?.map {
|
||||
sources.add(
|
||||
ExtractorLink(
|
||||
name,
|
||||
name,
|
||||
it.file,
|
||||
referer = mainUrl,
|
||||
quality = when {
|
||||
it.label?.contains("HD") == true -> Qualities.P720.value
|
||||
it.label?.contains("SD") == true -> Qualities.P480.value
|
||||
else -> getQualityFromName(it.label)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
return sources
|
||||
}
|
||||
|
||||
private data class ResponseSource(
|
||||
@JsonProperty("file") val file: String,
|
||||
@JsonProperty("type") val type: String?,
|
||||
@JsonProperty("label") val label: String?
|
||||
)
|
||||
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
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.Qualities
|
||||
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||
|
||||
class Meownime : JWPlayer() {
|
||||
override val name = "Meownime"
|
||||
override val mainUrl = "https://meownime.ltd"
|
||||
}
|
||||
|
||||
class DesuOdchan : JWPlayer() {
|
||||
override val name = "DesuOdchan"
|
||||
override val mainUrl = "https://desustream.me/odchan/"
|
||||
}
|
||||
|
||||
class DesuArcg : JWPlayer() {
|
||||
override val name = "DesuArcg"
|
||||
override val mainUrl = "https://desustream.me/arcg/"
|
||||
}
|
||||
|
||||
class DesuDrive : JWPlayer() {
|
||||
override val name = "DesuDrive"
|
||||
override val mainUrl = "https://desustream.me/desudrive/"
|
||||
}
|
||||
|
||||
class DesuOdvip : JWPlayer() {
|
||||
override val name = "DesuOdvip"
|
||||
override val mainUrl = "https://desustream.me/odvip/"
|
||||
}
|
||||
|
||||
open class JWPlayer : ExtractorApi() {
|
||||
override val name = "JWPlayer"
|
||||
override val mainUrl = "https://www.jwplayer.com"
|
||||
override val requiresReferer = false
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
|
||||
val sources = mutableListOf<ExtractorLink>()
|
||||
with(app.get(url).document) {
|
||||
val data = this.select("script").mapNotNull { script ->
|
||||
if (script.data().contains("sources: [")) {
|
||||
script.data().substringAfter("sources: [")
|
||||
.substringBefore("],").replace("'", "\"")
|
||||
} else if (script.data().contains("otakudesu('")) {
|
||||
script.data().substringAfter("otakudesu('")
|
||||
.substringBefore("');")
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
tryParseJson<List<ResponseSource>>("$data")?.map {
|
||||
sources.add(
|
||||
ExtractorLink(
|
||||
name,
|
||||
name,
|
||||
it.file,
|
||||
referer = url,
|
||||
quality = getQualityFromName(
|
||||
Regex("(\\d{3,4}p)").find(it.file)?.groupValues?.get(
|
||||
1
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
return sources
|
||||
}
|
||||
|
||||
private data class ResponseSource(
|
||||
@JsonProperty("file") val file: String,
|
||||
@JsonProperty("type") val type: String?,
|
||||
@JsonProperty("label") val label: String?
|
||||
)
|
||||
|
||||
}
|
|
@ -1,27 +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.M3u8Helper
|
||||
|
||||
|
||||
open class Jawcloud : ExtractorApi() {
|
||||
override var name = "Jawcloud"
|
||||
override var mainUrl = "https://jawcloud.co"
|
||||
override val requiresReferer = false
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
|
||||
val doc = app.get(url).document
|
||||
val urlString = doc.select("html body div source").attr("src")
|
||||
val sources = mutableListOf<ExtractorLink>()
|
||||
if (urlString.contains("m3u8"))
|
||||
M3u8Helper.generateM3u8(
|
||||
name,
|
||||
urlString,
|
||||
url,
|
||||
headers = app.get(url).headers.toMap()
|
||||
).forEach { link -> sources.add(link) }
|
||||
return sources
|
||||
}
|
||||
}
|
|
@ -1,46 +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.getQualityFromName
|
||||
|
||||
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?): List<ExtractorLink> {
|
||||
val id = url.substringAfter("id=")
|
||||
val sources = mutableListOf<ExtractorLink>()
|
||||
|
||||
app.get("$mainUrl/api/open/get_url?itemId=$id", referer=url).parsedSafe<Responses>()?.data?.rList?.map { link ->
|
||||
sources.add(
|
||||
ExtractorLink(
|
||||
name,
|
||||
name,
|
||||
link.url,
|
||||
url,
|
||||
getQualityFromName(link.resolution)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return sources
|
||||
}
|
||||
|
||||
data class RList(
|
||||
@JsonProperty("url") val url: String,
|
||||
@JsonProperty("resolution") val resolution: String?,
|
||||
)
|
||||
|
||||
data class Data(
|
||||
@JsonProperty("rList") val rList: List<RList>?,
|
||||
)
|
||||
|
||||
data class Responses(
|
||||
@JsonProperty("data") val data: Data?,
|
||||
)
|
||||
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
//{"auto":"/manifests/movies/15559/1624728920/qDwu5BOsfAwfTmnnjmkmXA/master.m3u8","1080p":"https://vdoc3.sallenes.space/qDwu5BOsfAwfTmnnjmkmXA/1624728920/storage6/movies/the-man-with-the-iron-heart-2017/1080p/index.m3u8","720p":"https://vdoc3.sallenes.space/qDwu5BOsfAwfTmnnjmkmXA/1624728920/storage6/movies/the-man-with-the-iron-heart-2017/720p/index.m3u8","360p":"https://vdoc3.sallenes.space/qDwu5BOsfAwfTmnnjmkmXA/1624728920/storage6/movies/the-man-with-the-iron-heart-2017/360p/index.m3u8","480p":"https://vdoc3.sallenes.space/qDwu5BOsfAwfTmnnjmkmXA/1624728920/storage6/movies/the-man-with-the-iron-heart-2017/480p/index.m3u8"}
|
||||
object M3u8Manifest {
|
||||
// URL = first, QUALITY = second
|
||||
fun extractLinks(m3u8Data: String): ArrayList<Pair<String, String>> {
|
||||
val data: ArrayList<Pair<String, String>> = ArrayList()
|
||||
Regex("\"(.*?)\":\"(.*?)\"").findAll(m3u8Data).forEach {
|
||||
var quality = it.groupValues[1].replace("auto", "Auto")
|
||||
if (quality != "Auto" && !quality.endsWith('p')) quality += "p"
|
||||
val url = it.groupValues[2]
|
||||
data.add(Pair(url, quality))
|
||||
}
|
||||
return data
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
|
||||
open class Maxstream : ExtractorApi() {
|
||||
override var name = "Maxstream"
|
||||
override var mainUrl = "https://maxstream.video/"
|
||||
override val requiresReferer = false
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
|
||||
val extractedLinksList: MutableList<ExtractorLink> = mutableListOf()
|
||||
val response = app.get(url).text
|
||||
val jstounpack = Regex("cript\">eval((.|\\n)*?)</script>").find(response)?.groups?.get(1)?.value
|
||||
val unpacjed = JsUnpacker(jstounpack).unpack()
|
||||
val extractedUrl = unpacjed?.let { Regex("""src:"((.|\n)*?)",type""").find(it) }?.groups?.get(1)?.value.toString()
|
||||
|
||||
M3u8Helper.generateM3u8(
|
||||
name,
|
||||
extractedUrl,
|
||||
url,
|
||||
headers = mapOf("referer" to url)
|
||||
).forEach { link ->
|
||||
extractedLinksList.add(link)
|
||||
}
|
||||
|
||||
return extractedLinksList
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
open class Mcloud : WcoStream() {
|
||||
override var name = "Mcloud"
|
||||
override var mainUrl = "https://mcloud.to"
|
||||
override val requiresReferer = true
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
|
||||
class MixDropBz : MixDrop(){
|
||||
override var mainUrl = "https://mixdrop.bz"
|
||||
}
|
||||
|
||||
class MixDropCh : MixDrop(){
|
||||
override var mainUrl = "https://mixdrop.ch"
|
||||
}
|
||||
class MixDropTo : MixDrop(){
|
||||
override var mainUrl = "https://mixdrop.to"
|
||||
}
|
||||
|
||||
open class MixDrop : ExtractorApi() {
|
||||
override var name = "MixDrop"
|
||||
override var mainUrl = "https://mixdrop.co"
|
||||
private val srcRegex = Regex("""wurl.*?=.*?"(.*?)";""")
|
||||
override val requiresReferer = false
|
||||
|
||||
override fun getExtractorUrl(id: String): String {
|
||||
return "$mainUrl/e/$id"
|
||||
}
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
|
||||
with(app.get(url)) {
|
||||
getAndUnpack(this.text).let { unpackedText ->
|
||||
srcRegex.find(unpackedText)?.groupValues?.get(1)?.let { link ->
|
||||
return listOf(
|
||||
ExtractorLink(
|
||||
name,
|
||||
name,
|
||||
httpsify(link),
|
||||
url,
|
||||
Qualities.Unknown.value,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return 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 Mp4Upload : ExtractorApi() {
|
||||
override var name = "Mp4Upload"
|
||||
override var mainUrl = "https://www.mp4upload.com"
|
||||
private val srcRegex = Regex("""player\.src\("(.*?)"""")
|
||||
override val requiresReferer = true
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
|
||||
with(app.get(url)) {
|
||||
getAndUnpack(this.text).let { unpackedText ->
|
||||
val quality = unpackedText.lowercase().substringAfter(" height=").substringBefore(" ").toIntOrNull()
|
||||
srcRegex.find(unpackedText)?.groupValues?.get(1)?.let { link ->
|
||||
return listOf(
|
||||
ExtractorLink(
|
||||
name,
|
||||
name,
|
||||
link,
|
||||
url,
|
||||
quality ?: Qualities.Unknown.value,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
|
@ -1,59 +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.getQualityFromName
|
||||
import java.net.URI
|
||||
|
||||
class MultiQuality : ExtractorApi() {
|
||||
override var name = "MultiQuality"
|
||||
override var mainUrl = "https://gogo-play.net"
|
||||
private val sourceRegex = Regex("""file:\s*['"](.*?)['"],label:\s*['"](.*?)['"]""")
|
||||
private val m3u8Regex = Regex(""".*?(\d*).m3u8""")
|
||||
private val urlRegex = Regex("""(.*?)([^/]+$)""")
|
||||
override val requiresReferer = false
|
||||
|
||||
override fun getExtractorUrl(id: String): String {
|
||||
return "$mainUrl/loadserver.php?id=$id"
|
||||
}
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> {
|
||||
val extractedLinksList: MutableList<ExtractorLink> = mutableListOf()
|
||||
with(app.get(url)) {
|
||||
sourceRegex.findAll(this.text).forEach { sourceMatch ->
|
||||
val extractedUrl = sourceMatch.groupValues[1]
|
||||
// Trusting this isn't mp4, may fuck up stuff
|
||||
if (URI(extractedUrl).path.endsWith(".m3u8")) {
|
||||
with(app.get(extractedUrl)) {
|
||||
m3u8Regex.findAll(this.text).forEach { match ->
|
||||
extractedLinksList.add(
|
||||
ExtractorLink(
|
||||
name,
|
||||
name = name,
|
||||
urlRegex.find(this.url)!!.groupValues[1] + match.groupValues[0],
|
||||
url,
|
||||
getQualityFromName(match.groupValues[1]),
|
||||
isM3u8 = true
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
} else if (extractedUrl.endsWith(".mp4")) {
|
||||
extractedLinksList.add(
|
||||
ExtractorLink(
|
||||
name,
|
||||
"$name ${sourceMatch.groupValues[2]}",
|
||||
extractedUrl,
|
||||
url.replace(" ", "%20"),
|
||||
Qualities.Unknown.value,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
return extractedLinksList
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,67 +0,0 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||
|
||||
data class DataOptionsJson (
|
||||
@JsonProperty("flashvars") var flashvars : Flashvars? = Flashvars(),
|
||||
)
|
||||
data class Flashvars (
|
||||
@JsonProperty("metadata") var metadata : String? = null,
|
||||
@JsonProperty("hlsManifestUrl") var hlsManifestUrl : String? = null, //m3u8
|
||||
)
|
||||
|
||||
data class MetadataOkru (
|
||||
@JsonProperty("videos") var videos: ArrayList<Videos> = arrayListOf(),
|
||||
)
|
||||
|
||||
data class Videos (
|
||||
@JsonProperty("name") var name : String,
|
||||
@JsonProperty("url") var url : String,
|
||||
@JsonProperty("seekSchema") var seekSchema : Int? = null,
|
||||
@JsonProperty("disallowed") var disallowed : Boolean? = null
|
||||
)
|
||||
|
||||
class OkRuHttps: OkRu(){
|
||||
override var mainUrl = "https://ok.ru"
|
||||
}
|
||||
|
||||
open class OkRu : ExtractorApi() {
|
||||
override var name = "Okru"
|
||||
override var mainUrl = "http://ok.ru"
|
||||
override val requiresReferer = false
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
|
||||
val doc = app.get(url).document
|
||||
val sources = ArrayList<ExtractorLink>()
|
||||
val datajson = doc.select("div[data-options]").attr("data-options")
|
||||
if (datajson.isNotBlank()) {
|
||||
val main = parseJson<DataOptionsJson>(datajson)
|
||||
val metadatajson = parseJson<MetadataOkru>(main.flashvars?.metadata!!)
|
||||
val servers = metadatajson.videos
|
||||
servers.forEach {
|
||||
val quality = it.name.uppercase()
|
||||
.replace("MOBILE","144p")
|
||||
.replace("LOWEST","240p")
|
||||
.replace("LOW","360p")
|
||||
.replace("SD","480p")
|
||||
.replace("HD","720p")
|
||||
.replace("FULL","1080p")
|
||||
.replace("QUAD","1440p")
|
||||
.replace("ULTRA","4k")
|
||||
val extractedurl = it.url.replace("\\\\u0026", "&")
|
||||
sources.add(ExtractorLink(
|
||||
name,
|
||||
name = this.name,
|
||||
extractedurl,
|
||||
url,
|
||||
getQualityFromName(quality),
|
||||
isM3u8 = false
|
||||
))
|
||||
}
|
||||
}
|
||||
return sources
|
||||
}
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
import com.lagradost.cloudstream3.SubtitleFile
|
||||
import com.lagradost.cloudstream3.apmap
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.extractorApis
|
||||
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
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.
|
||||
* */
|
||||
class Pelisplus(val mainUrl: String) {
|
||||
val name: String = "Vidstream"
|
||||
|
||||
private fun getExtractorUrl(id: String): String {
|
||||
return "$mainUrl/play?id=$id"
|
||||
}
|
||||
|
||||
private fun getDownloadUrl(id: String): String {
|
||||
return "$mainUrl/download?id=$id"
|
||||
}
|
||||
|
||||
private val normalApis = arrayListOf(MultiQuality())
|
||||
|
||||
// https://gogo-stream.com/streaming.php?id=MTE3NDg5
|
||||
suspend fun getUrl(
|
||||
id: String,
|
||||
isCasting: Boolean = false,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
try {
|
||||
normalApis.apmap { api ->
|
||||
val url = api.getExtractorUrl(id)
|
||||
api.getSafeUrl(url, subtitleCallback = subtitleCallback, callback = callback)
|
||||
}
|
||||
val extractorUrl = getExtractorUrl(id)
|
||||
|
||||
/** Stolen from GogoanimeProvider.kt extractor */
|
||||
suspendSafeApiCall {
|
||||
val link = getDownloadUrl(id)
|
||||
println("Generated vidstream download link: $link")
|
||||
val page = app.get(link, referer = extractorUrl)
|
||||
|
||||
val pageDoc = Jsoup.parse(page.text)
|
||||
val qualityRegex = Regex("(\\d+)P")
|
||||
|
||||
//a[download]
|
||||
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()
|
||||
.toString()
|
||||
|
||||
if (!loadExtractor(href, link, subtitleCallback, callback)) {
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
this.name,
|
||||
name = this.name,
|
||||
href,
|
||||
page.url,
|
||||
getQualityFromName(qual),
|
||||
element.attr("href").contains(".m3u8")
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
with(app.get(extractorUrl)) {
|
||||
val document = Jsoup.parse(this.text)
|
||||
val primaryLinks = document.select("ul.list-server-items > li.linkserver")
|
||||
//val extractedLinksList: MutableList<ExtractorLink> = mutableListOf()
|
||||
|
||||
// All vidstream links passed to extractors
|
||||
primaryLinks.distinctBy { it.attr("data-video") }.forEach { element ->
|
||||
val link = element.attr("data-video")
|
||||
//val name = element.text()
|
||||
|
||||
// Matches vidstream links with extractors
|
||||
extractorApis.filter { !it.requiresReferer || !isCasting }.apmap { api ->
|
||||
if (link.startsWith(api.mainUrl)) {
|
||||
api.getSafeUrl(link, extractorUrl, subtitleCallback, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,31 +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.M3u8Helper
|
||||
|
||||
|
||||
open class PlayerVoxzer : ExtractorApi() {
|
||||
override var name = "Voxzer"
|
||||
override var mainUrl = "https://player.voxzer.org"
|
||||
override val requiresReferer = false
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
|
||||
val listurl = url.replace("/view/","/list/")
|
||||
val urltext = app.get(listurl, referer = url).text
|
||||
val m3u8regex = Regex("((https:|http:)\\/\\/.*\\.m3u8)")
|
||||
val sources = mutableListOf<ExtractorLink>()
|
||||
val listm3 = m3u8regex.find(urltext)?.value
|
||||
if (listm3?.contains("m3u8") == true)
|
||||
M3u8Helper.generateM3u8(
|
||||
name,
|
||||
listm3,
|
||||
url,
|
||||
headers = app.get(url).headers.toMap()
|
||||
).forEach { link ->
|
||||
sources.add(link)
|
||||
}
|
||||
return sources
|
||||
}
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
import com.lagradost.cloudstream3.APIHolder.unixTimeMS
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.utils.ExtractorApi
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
import com.lagradost.cloudstream3.utils.getPostForm
|
||||
import org.jsoup.Jsoup
|
||||
|
||||
//class SBPlay1 : SBPlay() {
|
||||
// override var mainUrl = "https://sbplay1.com"
|
||||
//}
|
||||
|
||||
//class SBPlay2 : SBPlay() {
|
||||
// override var mainUrl = "https://sbplay2.com"
|
||||
//}
|
||||
|
||||
open class SBPlay : ExtractorApi() {
|
||||
override var mainUrl = "https://sbplay.one"
|
||||
override var name = "SBPlay"
|
||||
override val requiresReferer = false
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> {
|
||||
val response = app.get(url, referer = referer).text
|
||||
val document = Jsoup.parse(response)
|
||||
|
||||
val links = ArrayList<ExtractorLink>()
|
||||
|
||||
val tree = document.select("table > tbody > tr > td > a")
|
||||
for (item in tree) {
|
||||
val onDownload = item.attr("onclick")
|
||||
val name = "${this.name} - ${item.text()}"
|
||||
try {
|
||||
Regex("download_video\\('(.*?)','(.*?)','(.*?)'\\)").matchEntire(onDownload)?.let {
|
||||
val id = it.groupValues[1]
|
||||
val mode = it.groupValues[2]
|
||||
val hash = it.groupValues[3]
|
||||
val href = "https://sbplay.one/dl?op=download_orig&id=$id&mode=$mode&hash=$hash"
|
||||
val hrefResponse = app.get(href).text
|
||||
app.post("https://sbplay.one/?op=notifications&open=&_=$unixTimeMS", referer = href)
|
||||
val hrefDocument = Jsoup.parse(hrefResponse)
|
||||
val hrefSpan = hrefDocument.selectFirst("span > a")
|
||||
if (hrefSpan == null) {
|
||||
getPostForm(href, hrefResponse)?.let { form ->
|
||||
val postDocument = Jsoup.parse(form)
|
||||
val downloadBtn = postDocument.selectFirst("a.downloadbtn")?.attr("href")
|
||||
if (downloadBtn.isNullOrEmpty()) {
|
||||
val hrefSpan2 = postDocument.selectFirst("span > a")?.attr("href")
|
||||
if (hrefSpan2?.startsWith("https://") == true) {
|
||||
links.add(
|
||||
ExtractorLink(
|
||||
this.name, name,
|
||||
hrefSpan2, "", Qualities.Unknown.value, false
|
||||
)
|
||||
)
|
||||
} else {
|
||||
// no link found!!!
|
||||
}
|
||||
} else {
|
||||
links.add(
|
||||
ExtractorLink(
|
||||
this.name,
|
||||
name,
|
||||
downloadBtn,
|
||||
"",
|
||||
Qualities.Unknown.value,
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val link = hrefSpan.attr("href")
|
||||
links.add(ExtractorLink(this.name, name, link, "", Qualities.Unknown.value, false))
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
}
|
||||
}
|
||||
|
||||
return links
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
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.getQualityFromName
|
||||
|
||||
|
||||
class Solidfiles : ExtractorApi() {
|
||||
override val name = "Solidfiles"
|
||||
override val mainUrl = "https://www.solidfiles.com"
|
||||
override val requiresReferer = false
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> {
|
||||
val sources = mutableListOf<ExtractorLink>()
|
||||
with(app.get(url).document) {
|
||||
this.select("script").map { script ->
|
||||
if (script.data().contains("\"streamUrl\":")) {
|
||||
val data = script.data().substringAfter("constant('viewerOptions', {").substringBefore("});")
|
||||
val source = tryParseJson<ResponseSource>("{$data}")
|
||||
val quality = Regex("\\d{3,4}p").find(source!!.nodeName)?.groupValues?.get(0)
|
||||
sources.add(
|
||||
ExtractorLink(
|
||||
name,
|
||||
name,
|
||||
source.streamUrl,
|
||||
referer = url,
|
||||
quality = getQualityFromName(quality)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return sources
|
||||
}
|
||||
|
||||
|
||||
private data class ResponseSource(
|
||||
@JsonProperty("streamUrl") val streamUrl: String,
|
||||
@JsonProperty("nodeName") val nodeName: String
|
||||
)
|
||||
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
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
|
||||
|
||||
class SpeedoStream : ExtractorApi() {
|
||||
override val name = "SpeedoStream"
|
||||
override val mainUrl = "https://speedostream.com"
|
||||
override val requiresReferer = true
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> {
|
||||
val sources = mutableListOf<ExtractorLink>()
|
||||
app.get(url, referer = referer).document.select("script").map { script ->
|
||||
if (script.data().contains("jwplayer(\"vplayer\").setup(")) {
|
||||
val data = script.data().substringAfter("sources: [")
|
||||
.substringBefore("],").replace("file", "\"file\"").trim()
|
||||
tryParseJson<File>(data)?.let {
|
||||
M3u8Helper.generateM3u8(
|
||||
name,
|
||||
it.file,
|
||||
"$mainUrl/",
|
||||
).forEach { m3uData -> sources.add(m3uData) }
|
||||
}
|
||||
}
|
||||
}
|
||||
return sources
|
||||
}
|
||||
|
||||
private data class File(
|
||||
@JsonProperty("file") val file: String,
|
||||
)
|
||||
|
||||
|
||||
}
|
|
@ -1,126 +0,0 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
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 Ssbstream : StreamSB() {
|
||||
override var mainUrl = "https://ssbstream.net"
|
||||
}
|
||||
|
||||
class SBfull : StreamSB() {
|
||||
override var mainUrl = "https://sbfull.com"
|
||||
}
|
||||
|
||||
class StreamSB1 : StreamSB() {
|
||||
override var mainUrl = "https://sbplay1.com"
|
||||
}
|
||||
|
||||
class StreamSB2 : StreamSB() {
|
||||
override var mainUrl = "https://sbplay2.com"
|
||||
}
|
||||
|
||||
class StreamSB3 : StreamSB() {
|
||||
override var mainUrl = "https://sbplay3.com"
|
||||
}
|
||||
|
||||
class StreamSB4 : StreamSB() {
|
||||
override var mainUrl = "https://cloudemb.com"
|
||||
}
|
||||
|
||||
class StreamSB5 : StreamSB() {
|
||||
override var mainUrl = "https://sbplay.org"
|
||||
}
|
||||
|
||||
class StreamSB6 : StreamSB() {
|
||||
override var mainUrl = "https://embedsb.com"
|
||||
}
|
||||
|
||||
class StreamSB7 : StreamSB() {
|
||||
override var mainUrl = "https://pelistop.co"
|
||||
}
|
||||
|
||||
class StreamSB8 : StreamSB() {
|
||||
override var mainUrl = "https://streamsb.net"
|
||||
}
|
||||
|
||||
class StreamSB9 : StreamSB() {
|
||||
override var mainUrl = "https://sbplay.one"
|
||||
}
|
||||
|
||||
class StreamSB10 : StreamSB() {
|
||||
override var mainUrl = "https://sbplay2.xyz"
|
||||
}
|
||||
|
||||
// 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() {
|
||||
override var name = "StreamSB"
|
||||
override var mainUrl = "https://watchsb.com"
|
||||
override val requiresReferer = false
|
||||
|
||||
private val hexArray = "0123456789ABCDEF".toCharArray()
|
||||
|
||||
private fun bytesToHex(bytes: ByteArray): String {
|
||||
val hexChars = CharArray(bytes.size * 2)
|
||||
for (j in bytes.indices) {
|
||||
val v = bytes[j].toInt() and 0xFF
|
||||
|
||||
hexChars[j * 2] = hexArray[v ushr 4]
|
||||
hexChars[j * 2 + 1] = hexArray[v and 0x0F]
|
||||
}
|
||||
return String(hexChars)
|
||||
}
|
||||
|
||||
data class Subs (
|
||||
@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: List<Subs>?,
|
||||
@JsonProperty("length") val length: String,
|
||||
@JsonProperty("id") val id: String,
|
||||
@JsonProperty("title") val title: String,
|
||||
@JsonProperty("backup") val backup: String,
|
||||
)
|
||||
|
||||
data class Main (
|
||||
@JsonProperty("stream_data") val streamData: StreamData,
|
||||
@JsonProperty("status_code") val statusCode: Int,
|
||||
)
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
|
||||
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\\/)"),"")
|
||||
}.first()
|
||||
val bytes = id.toByteArray()
|
||||
val bytesToHex = bytesToHex(bytes)
|
||||
val master = "$mainUrl/sources43/6d6144797752744a454267617c7c${bytesToHex.lowercase()}7c7c4e61755a56456f34385243727c7c73747265616d7362/6b4a33767968506e4e71374f7c7c343837323439333133333462353935333633373836643638376337633462333634663539343137373761333635313533333835333763376333393636363133393635366136323733343435323332376137633763373337343732363536313664373336327c7c504d754478413835306633797c7c73747265616d7362"
|
||||
val headers = mapOf(
|
||||
"watchsb" to "streamsb",
|
||||
)
|
||||
val urltext = app.get(master,
|
||||
headers = headers,
|
||||
allowRedirects = false
|
||||
).text
|
||||
val mapped = urltext.let { parseJson<Main>(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
|
||||
}
|
||||
}
|
|
@ -1,33 +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
|
||||
|
||||
class StreamTape : ExtractorApi() {
|
||||
override var name = "StreamTape"
|
||||
override var mainUrl = "https://streamtape.com"
|
||||
override val requiresReferer = false
|
||||
|
||||
private val linkRegex =
|
||||
Regex("""'robotlink'\)\.innerHTML = '(.+?)'\+ \('(.+?)'\)""")
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
|
||||
with(app.get(url)) {
|
||||
linkRegex.find(this.text)?.let {
|
||||
val extractedUrl = "https:${it.groups[1]!!.value + it.groups[2]!!.value.substring(3,)}"
|
||||
return listOf(
|
||||
ExtractorLink(
|
||||
name,
|
||||
name,
|
||||
extractedUrl,
|
||||
url,
|
||||
Qualities.Unknown.value,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
|
@ -1,39 +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.JsUnpacker
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
import java.net.URI
|
||||
|
||||
class Streamhub : ExtractorApi() {
|
||||
override var mainUrl = "https://streamhub.to"
|
||||
override var name = "Streamhub"
|
||||
override val requiresReferer = false
|
||||
|
||||
override fun getExtractorUrl(id: String): String {
|
||||
return "$mainUrl/e/$id"
|
||||
}
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
|
||||
val response = app.get(url).text
|
||||
Regex("eval((.|\\n)*?)</script>").find(response)?.groupValues?.get(1)?.let { jsEval ->
|
||||
JsUnpacker("eval$jsEval").unpack()?.let { unPacked ->
|
||||
Regex("sources:\\[\\{src:\"(.*?)\"").find(unPacked)?.groupValues?.get(1)?.let { link ->
|
||||
return listOf(
|
||||
ExtractorLink(
|
||||
this.name,
|
||||
this.name,
|
||||
link,
|
||||
referer ?: "",
|
||||
Qualities.Unknown.value,
|
||||
URI(link).path.endsWith(".m3u8")
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
|
@ -1,60 +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
|
||||
import com.lagradost.nicehttp.RequestBodyTypes
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
|
||||
|
||||
class Streamlare : Slmaxed() {
|
||||
override val mainUrl = "https://streamlare.com/"
|
||||
}
|
||||
|
||||
open class Slmaxed : ExtractorApi() {
|
||||
override val name = "Streamlare"
|
||||
override val mainUrl = "https://slmaxed.com/"
|
||||
override val requiresReferer = true
|
||||
|
||||
// https://slmaxed.com/e/oLvgezw3LjPzbp8E -> oLvgezw3LjPzbp8E
|
||||
val embedRegex = Regex("""/e/([^/]*)""")
|
||||
|
||||
|
||||
data class JsonResponse(
|
||||
@JsonProperty val status: String? = null,
|
||||
@JsonProperty val message: String? = null,
|
||||
@JsonProperty val type: String? = null,
|
||||
@JsonProperty val token: String? = null,
|
||||
@JsonProperty val result: Map<String, Result>? = null
|
||||
)
|
||||
|
||||
data class Result(
|
||||
@JsonProperty val label: String? = null,
|
||||
@JsonProperty val file: String? = null,
|
||||
@JsonProperty val type: String? = null
|
||||
)
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
|
||||
val id = embedRegex.find(url)!!.groupValues[1]
|
||||
val json = app.post(
|
||||
"${mainUrl}api/video/stream/get",
|
||||
requestBody = """{"id":"$id"}""".toRequestBody(RequestBodyTypes.JSON.toMediaTypeOrNull())
|
||||
).parsed<JsonResponse>()
|
||||
return json.result?.mapNotNull {
|
||||
it.value.let { result ->
|
||||
ExtractorLink(
|
||||
this.name,
|
||||
this.name,
|
||||
result.file ?: return@mapNotNull null,
|
||||
url,
|
||||
result.label?.replace("p", "", ignoreCase = true)?.trim()?.toIntOrNull()
|
||||
?: Qualities.Unknown.value,
|
||||
isM3u8 = result.type?.contains("hls", ignoreCase = true) == true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||
|
||||
data class Files(
|
||||
@JsonProperty("file") val id: String,
|
||||
@JsonProperty("label") val label: String? = null,
|
||||
)
|
||||
|
||||
open class Supervideo : ExtractorApi() {
|
||||
override var name = "Supervideo"
|
||||
override var mainUrl = "https://supervideo.tv"
|
||||
override val requiresReferer = false
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
|
||||
val extractedLinksList: MutableList<ExtractorLink> = mutableListOf()
|
||||
val response = app.get(url).text
|
||||
val jstounpack = Regex("eval((.|\\n)*?)</script>").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 parsedlinks = parseJson<List<Files>>(extractedUrl)
|
||||
parsedlinks.forEach { data ->
|
||||
if (data.label.isNullOrBlank()){ // mp4 links (with labels) are slow. Use only m3u8 link.
|
||||
M3u8Helper.generateM3u8(
|
||||
name,
|
||||
data.id,
|
||||
url,
|
||||
headers = mapOf("referer" to url)
|
||||
).forEach { link ->
|
||||
extractedLinksList.add(link)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return extractedLinksList
|
||||
}
|
||||
}
|
|
@ -1,42 +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.AppUtils.parseJson
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
|
||||
open class Tantifilm : ExtractorApi() {
|
||||
override var name = "Tantifilm"
|
||||
override var mainUrl = "https://cercafilm.net"
|
||||
override val requiresReferer = false
|
||||
|
||||
data class TantifilmJsonData (
|
||||
@JsonProperty("success") val success : Boolean,
|
||||
@JsonProperty("data") val data : List<TantifilmData>,
|
||||
@JsonProperty("captions")val captions : List<String>,
|
||||
@JsonProperty("is_vr") val is_vr : Boolean
|
||||
)
|
||||
|
||||
data class TantifilmData (
|
||||
@JsonProperty("file") val file : String,
|
||||
@JsonProperty("label") val label : String,
|
||||
@JsonProperty("type") val type : String
|
||||
)
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
|
||||
val link = "$mainUrl/api/source/${url.substringAfterLast("/")}"
|
||||
val response = app.post(link).text.replace("""\""","")
|
||||
val jsonvideodata = parseJson<TantifilmJsonData>(response)
|
||||
return jsonvideodata.data.map {
|
||||
ExtractorLink(
|
||||
it.file+".${it.type}",
|
||||
this.name,
|
||||
it.file+".${it.type}",
|
||||
mainUrl,
|
||||
it.label.filter{ it.isDigit() }.toInt(),
|
||||
false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
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 = "Cinestart"
|
||||
override var mainUrl = "https://cinestart.net"
|
||||
override val details = "vr.php?v="
|
||||
}
|
||||
|
||||
open class Tomatomatela : ExtractorApi() {
|
||||
override var name = "Tomatomatela"
|
||||
override var mainUrl = "https://tomatomatela.com"
|
||||
override val requiresReferer = false
|
||||
private data class Tomato (
|
||||
@JsonProperty("status") val status: Int,
|
||||
@JsonProperty("file") val file: String
|
||||
)
|
||||
open val details = "details.php?v="
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
|
||||
val link = url.replace("$mainUrl/embed.html#","$mainUrl/$details")
|
||||
val server = app.get(link, allowRedirects = false).text
|
||||
val json = parseJson<Tomato>(server)
|
||||
if (json.status == 200) return listOf(
|
||||
ExtractorLink(
|
||||
name,
|
||||
name,
|
||||
json.file,
|
||||
"",
|
||||
Qualities.Unknown.value,
|
||||
isM3u8 = false
|
||||
)
|
||||
)
|
||||
return null
|
||||
}
|
||||
}
|
|
@ -1,59 +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
|
||||
|
||||
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?): List<ExtractorLink> {
|
||||
// WIP: m3u8 link fetched but sometimes not playing
|
||||
//Log.i(this.name, "Result => (no extractor) ${url}")
|
||||
val sources: MutableList<ExtractorLink> = mutableListOf()
|
||||
val doc = app.get(url, referer = referer).text
|
||||
if (doc.isNotBlank()) {
|
||||
var reg = Regex("(?<=master)(.*)(?=hls)")
|
||||
val result = reg.find(doc)?.groupValues?.map {
|
||||
it.trim('|')
|
||||
}?.toList()
|
||||
reg = Regex("(?<=\\|file\\|)(.*)(?=\\|remove\\|)")
|
||||
val domainList = reg.find(doc)?.groupValues?.get(1)?.split("|")
|
||||
var domain = when (!domainList.isNullOrEmpty()) {
|
||||
true -> {
|
||||
if (domainList.isNotEmpty()) {
|
||||
var domName = ""
|
||||
for (part in domainList) {
|
||||
domName = "${part}.${domName}"
|
||||
}
|
||||
domName.trimEnd('.')
|
||||
} else { "" }
|
||||
}
|
||||
false -> ""
|
||||
}
|
||||
//Log.i(this.name, "Result => (domain) ${domain}")
|
||||
if (domain.isEmpty()) {
|
||||
domain = "s96.upstreamcdn.co"
|
||||
//Log.i(this.name, "Result => (default domain) ${domain}")
|
||||
}
|
||||
|
||||
result?.forEach {
|
||||
val linkUrl = "https://${domain}/hls/${it}/master.m3u8"
|
||||
sources.add(
|
||||
ExtractorLink(
|
||||
name = "Upstream m3u8",
|
||||
source = this.name,
|
||||
url = linkUrl,
|
||||
quality = Qualities.Unknown.value,
|
||||
referer = referer ?: linkUrl,
|
||||
isM3u8 = true
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
return sources
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
import com.lagradost.cloudstream3.app
|
||||
|
||||
class Uqload1 : Uqload() {
|
||||
override var mainUrl = "https://uqload.com"
|
||||
}
|
||||
|
||||
open class Uqload : ExtractorApi() {
|
||||
override val name: String = "Uqload"
|
||||
override val mainUrl: String = "https://www.uqload.com"
|
||||
private val srcRegex = Regex("""sources:.\[(.*?)\]""") // would be possible to use the parse and find src attribute
|
||||
override val requiresReferer = true
|
||||
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
|
||||
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 + flag,
|
||||
link,
|
||||
cleaned_url,
|
||||
Qualities.Unknown.value,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
|
@ -1,117 +0,0 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
import org.mozilla.javascript.Context
|
||||
import org.mozilla.javascript.EvaluatorException
|
||||
import org.mozilla.javascript.Scriptable
|
||||
import java.util.*
|
||||
|
||||
|
||||
open class Userload : ExtractorApi() {
|
||||
override var name = "Userload"
|
||||
override var mainUrl = "https://userload.co"
|
||||
override val requiresReferer = false
|
||||
|
||||
private fun splitInput(input: String): List<String> {
|
||||
var counter = 0
|
||||
val array = ArrayList<String>()
|
||||
var buffer = ""
|
||||
for (c in input) {
|
||||
when (c) {
|
||||
'(' -> counter++
|
||||
')' -> counter--
|
||||
else -> {}
|
||||
}
|
||||
buffer += c
|
||||
if (counter == 0) {
|
||||
if (buffer.isNotBlank() && buffer != "+")
|
||||
array.add(buffer)
|
||||
buffer = ""
|
||||
}
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
private fun evaluateMath(mathExpression : String): String {
|
||||
val rhino = Context.enter()
|
||||
rhino.initStandardObjects()
|
||||
rhino.optimizationLevel = -1
|
||||
val scope: Scriptable = rhino.initStandardObjects()
|
||||
return try {
|
||||
rhino.evaluateString(scope, "eval($mathExpression)", "JavaScript", 1, null).toString()
|
||||
}
|
||||
catch (e: EvaluatorException){
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
private fun decodeVideoJs(text: String): List<String> {
|
||||
text.replace("""\s+|/\*.*?\*/""".toRegex(), "")
|
||||
val data = text.split("""+(゚Д゚)[゚o゚]""")[1]
|
||||
val chars = data.split("""+ (゚Д゚)[゚ε゚]+""").drop(1)
|
||||
val newchars = chars.map { char ->
|
||||
char.replace("(o゚ー゚o)", "u")
|
||||
.replace("c", "0")
|
||||
.replace("(゚Д゚)['0']", "c")
|
||||
.replace("゚Θ゚", "1")
|
||||
.replace("!+[]", "1")
|
||||
.replace("-~", "1+")
|
||||
.replace("o", "3")
|
||||
.replace("_", "3")
|
||||
.replace("゚ー゚", "4")
|
||||
.replace("(+", "(")
|
||||
}
|
||||
|
||||
val subchar = mutableListOf<String>()
|
||||
|
||||
newchars.dropLast(1).forEach { v ->
|
||||
subchar.add(splitInput(v).map { evaluateMath(it).substringBefore(".") }.toString().filter { it.isDigit() })
|
||||
}
|
||||
var txtresult = ""
|
||||
subchar.forEach{
|
||||
txtresult = txtresult.plus(Char(it.toInt(8)))
|
||||
}
|
||||
val val1 = Regex(""""morocco="((.|\\n)*?)"&mycountry="""").find(txtresult)?.groups?.get(1)?.value.toString().drop(1).dropLast(1)
|
||||
val val2 = txtresult.substringAfter("""&mycountry="+""").substringBefore(")")
|
||||
|
||||
return listOf(
|
||||
val1,
|
||||
val2
|
||||
)
|
||||
|
||||
|
||||
}
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink>? {
|
||||
|
||||
val extractedLinksList: MutableList<ExtractorLink> = mutableListOf()
|
||||
|
||||
val response = app.get(url).text
|
||||
val jsToUnpack = Regex("ext/javascript\">eval((.|\\n)*?)</script>").find(response)?.groups?.get(1)?.value
|
||||
val unpacked = JsUnpacker(jsToUnpack).unpack()
|
||||
val videoJs = app.get("$mainUrl/api/assets/userload/js/videojs.js")
|
||||
val videoJsToDecode = videoJs.text
|
||||
val values = decodeVideoJs(videoJsToDecode)
|
||||
val morocco = unpacked!!.split(";").filter { it.contains(values[0]) }[0].split("=")[1].drop(1).dropLast(1)
|
||||
val mycountry = unpacked.split(";").filter { it.contains(values[1]) }[0].split("=")[1].drop(1).dropLast(1)
|
||||
val videoLinkPage = app.post("$mainUrl/api/request/", data = mapOf(
|
||||
"morocco" to morocco,
|
||||
"mycountry" to mycountry
|
||||
))
|
||||
val videoLink = videoLinkPage.text
|
||||
val nameSource = app.get(url).document.head().selectFirst("title")!!.text()
|
||||
extractedLinksList.add(
|
||||
ExtractorLink(
|
||||
name,
|
||||
name,
|
||||
videoLink,
|
||||
mainUrl,
|
||||
getQualityFromName(nameSource),
|
||||
)
|
||||
)
|
||||
|
||||
return extractedLinksList
|
||||
}
|
||||
}
|
|
@ -1,68 +0,0 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
import com.lagradost.cloudstream3.SubtitleFile
|
||||
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
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
|
||||
class VidSrcExtractor2 : VidSrcExtractor() {
|
||||
override val mainUrl = "https://vidsrc.me/embed"
|
||||
override suspend fun getUrl(
|
||||
url: String,
|
||||
referer: String?,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
val newUrl = url.lowercase().replace(mainUrl, super.mainUrl)
|
||||
super.getUrl(newUrl, referer, subtitleCallback, callback)
|
||||
}
|
||||
}
|
||||
|
||||
open class VidSrcExtractor : ExtractorApi() {
|
||||
override val name = "VidSrc"
|
||||
private val absoluteUrl = "https://v2.vidsrc.me"
|
||||
override val mainUrl = "$absoluteUrl/embed"
|
||||
override val requiresReferer = false
|
||||
|
||||
override suspend fun getUrl(
|
||||
url: String,
|
||||
referer: String?,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
val iframedoc = app.get(url).document
|
||||
|
||||
val serverslist =
|
||||
iframedoc.select("div#sources.button_content div#content div#list div").map {
|
||||
val datahash = it.attr("data-hash")
|
||||
if (datahash.isNotBlank()) {
|
||||
val links = try {
|
||||
app.get("$absoluteUrl/src/$datahash", referer = "https://source.vidsrc.me/").url
|
||||
} catch (e: Exception) {
|
||||
""
|
||||
}
|
||||
links
|
||||
} else ""
|
||||
}
|
||||
|
||||
serverslist.apmap { server ->
|
||||
val linkfixed = server.replace("https://vidsrc.xyz/", "https://embedsito.com/")
|
||||
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@apmap
|
||||
M3u8Helper.generateM3u8(
|
||||
name,
|
||||
srcm3u8,
|
||||
absoluteUrl
|
||||
).forEach(callback)
|
||||
} else {
|
||||
loadExtractor(linkfixed, url, subtitleCallback, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,271 +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.M3u8Helper.Companion.generateM3u8
|
||||
import kotlinx.coroutines.delay
|
||||
import java.math.BigInteger
|
||||
|
||||
class VideovardSX : WcoStream() {
|
||||
override var mainUrl = "https://videovard.sx"
|
||||
}
|
||||
|
||||
class VideoVard : ExtractorApi() {
|
||||
override var name = "Videovard" // Cause works for animekisa and wco
|
||||
override var mainUrl = "https://videovard.to"
|
||||
override val requiresReferer = false
|
||||
|
||||
//The following code was extracted from https://github.com/saikou-app/saikou/blob/main/app/src/main/java/ani/saikou/parsers/anime/extractors/VideoVard.kt
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> {
|
||||
val id = url.substringAfter("e/").substringBefore("/")
|
||||
val sources = mutableListOf<ExtractorLink>()
|
||||
val hash = app.get("$mainUrl/api/make/download/$id").parsed<HashResponse>()
|
||||
delay(11_000)
|
||||
val resm3u8 = app.post(
|
||||
"$mainUrl/api/player/setup",
|
||||
mapOf("Referer" to "$mainUrl/"),
|
||||
data = mapOf(
|
||||
"cmd" to "get_stream",
|
||||
"file_code" to id,
|
||||
"hash" to hash.hash!!
|
||||
)
|
||||
).parsed<SetupResponse>()
|
||||
val m3u8 = decode(resm3u8.src!!, resm3u8.seed)
|
||||
sources.addAll(
|
||||
generateM3u8(
|
||||
name,
|
||||
m3u8,
|
||||
mainUrl,
|
||||
headers = mapOf("Referer" to mainUrl)
|
||||
)
|
||||
)
|
||||
return sources
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val big0 = 0.toBigInteger()
|
||||
private val big3 = 3.toBigInteger()
|
||||
private val big4 = 4.toBigInteger()
|
||||
private val big15 = 15.toBigInteger()
|
||||
private val big16 = 16.toBigInteger()
|
||||
private val big255 = 255.toBigInteger()
|
||||
|
||||
private fun decode(dataFile: String, seed: String): String {
|
||||
val dataSeed = replace(seed)
|
||||
val newDataSeed = binaryDigest(dataSeed)
|
||||
val newDataFile = bytes2blocks(ascii2bytes(dataFile))
|
||||
var list = listOf(1633837924, 1650680933).map { it.toBigInteger() }
|
||||
val xorList = mutableListOf<BigInteger>()
|
||||
for (i in newDataFile.indices step 2) {
|
||||
val temp = newDataFile.slice(i..i + 1)
|
||||
xorList += xorBlocks(list, tearDecode(temp, newDataSeed))
|
||||
list = temp
|
||||
}
|
||||
|
||||
val result = replace(unPad(blocks2bytes(xorList)).map { it.toInt().toChar() }.joinToString(""))
|
||||
return padLastChars(result)
|
||||
}
|
||||
|
||||
private fun binaryDigest(input: String): List<BigInteger> {
|
||||
val keys = listOf(1633837924, 1650680933, 1667523942, 1684366951).map { it.toBigInteger() }
|
||||
var list1 = keys.slice(0..1)
|
||||
var list2 = list1
|
||||
val blocks = bytes2blocks(digestPad(input))
|
||||
|
||||
for (i in blocks.indices step 4) {
|
||||
list1 = tearCode(xorBlocks(blocks.slice(i..i + 1), list1), keys).toMutableList()
|
||||
list2 = tearCode(xorBlocks(blocks.slice(i + 2..i + 3), list2), keys).toMutableList()
|
||||
|
||||
val temp = list1[0]
|
||||
list1[0] = list1[1]
|
||||
list1[1] = list2[0]
|
||||
list2[0] = list2[1]
|
||||
list2[1] = temp
|
||||
}
|
||||
|
||||
return listOf(list1[0], list1[1], list2[0], list2[1])
|
||||
}
|
||||
|
||||
private fun tearDecode(a90: List<BigInteger>, a91: List<BigInteger>): MutableList<BigInteger> {
|
||||
var (a95, a96) = a90
|
||||
|
||||
var a97 = (-957401312).toBigInteger()
|
||||
for (_i in 0 until 32) {
|
||||
a96 -= ((((a95 shl 4) xor rShift(a95, 5)) + a95) xor (a97 + a91[rShift(a97, 11).and(3.toBigInteger()).toInt()]))
|
||||
a97 += 1640531527.toBigInteger()
|
||||
a95 -= ((((a96 shl 4) xor rShift(a96, 5)) + a96) xor (a97 + a91[a97.and(3.toBigInteger()).toInt()]))
|
||||
|
||||
}
|
||||
|
||||
return mutableListOf(a95, a96)
|
||||
}
|
||||
|
||||
private fun digestPad(string: String): List<BigInteger> {
|
||||
val empList = mutableListOf<BigInteger>()
|
||||
val length = string.length
|
||||
val extra = big15 - (length.toBigInteger() % big16)
|
||||
empList.add(extra)
|
||||
for (i in 0 until length) {
|
||||
empList.add(string[i].code.toBigInteger())
|
||||
}
|
||||
for (i in 0 until extra.toInt()) {
|
||||
empList.add(big0)
|
||||
}
|
||||
|
||||
return empList
|
||||
}
|
||||
|
||||
private fun bytes2blocks(a22: List<BigInteger>): List<BigInteger> {
|
||||
val empList = mutableListOf<BigInteger>()
|
||||
val length = a22.size
|
||||
var listIndex = 0
|
||||
|
||||
for (i in 0 until length) {
|
||||
val subIndex = i % 4
|
||||
val shiftedByte = a22[i] shl (3 - subIndex) * 8
|
||||
|
||||
if (subIndex == 0) {
|
||||
empList.add(shiftedByte)
|
||||
} else {
|
||||
empList[listIndex] = empList[listIndex] or shiftedByte
|
||||
}
|
||||
|
||||
if (subIndex == 3) listIndex += 1
|
||||
}
|
||||
|
||||
return empList
|
||||
}
|
||||
|
||||
private fun blocks2bytes(inp: List<BigInteger>): List<BigInteger> {
|
||||
val tempList = mutableListOf<BigInteger>()
|
||||
inp.indices.forEach { i ->
|
||||
tempList += (big255 and rShift(inp[i], 24))
|
||||
tempList += (big255 and rShift(inp[i], 16))
|
||||
tempList += (big255 and rShift(inp[i], 8))
|
||||
tempList += (big255 and inp[i])
|
||||
}
|
||||
return tempList
|
||||
}
|
||||
|
||||
private fun unPad(a46: List<BigInteger>): List<BigInteger> {
|
||||
val evenOdd = a46[0].toInt().mod(2)
|
||||
return (1 until (a46.size - evenOdd)).map {
|
||||
a46[it]
|
||||
}
|
||||
}
|
||||
|
||||
private fun xorBlocks(a76: List<BigInteger>, a77: List<BigInteger>): List<BigInteger> {
|
||||
return listOf(a76[0] xor a77[0], a76[1] xor a77[1])
|
||||
}
|
||||
|
||||
private fun rShift(input: BigInteger, by: Int): BigInteger {
|
||||
return (input.mod(4294967296.toBigInteger()) shr by)
|
||||
}
|
||||
|
||||
private fun tearCode(list1: List<BigInteger>, list2: List<BigInteger>): MutableList<BigInteger> {
|
||||
var a1 = list1[0]
|
||||
var a2 = list1[1]
|
||||
var temp = big0
|
||||
|
||||
for (_i in 0 until 32) {
|
||||
a1 += (a2 shl 4 xor rShift(a2, 5)) + a2 xor temp + list2[(temp and big3).toInt()]
|
||||
temp -= 1640531527.toBigInteger()
|
||||
a2 += (a1 shl 4 xor rShift(a1, 5)) + a1 xor temp + list2[(rShift(temp, 11) and big3).toInt()]
|
||||
}
|
||||
return mutableListOf(a1, a2)
|
||||
}
|
||||
|
||||
private fun ascii2bytes(input: String): List<BigInteger> {
|
||||
val abc = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||
val abcMap = abc.mapIndexed { i, c -> c to i.toBigInteger() }.toMap()
|
||||
var index = -1
|
||||
val length = input.length
|
||||
var listIndex = 0
|
||||
val bytes = mutableListOf<BigInteger>()
|
||||
|
||||
while (true) {
|
||||
for (i in input) {
|
||||
if (abc.contains(i)) {
|
||||
index++
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
bytes.add((abcMap[input.getOrNull(index)?:return bytes]!! * big4))
|
||||
|
||||
while (true) {
|
||||
index++
|
||||
if (abc.contains(input[index])) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var temp = abcMap[input[index]]!!
|
||||
|
||||
bytes[listIndex] = bytes[listIndex] or rShift(temp, 4)
|
||||
listIndex++
|
||||
temp = (big15.and(temp))
|
||||
|
||||
if ((temp == big0) && (index == (length - 1))) return bytes
|
||||
|
||||
bytes.add((temp * big4 * big4))
|
||||
|
||||
while (true) {
|
||||
index++
|
||||
if (index >= length) return bytes
|
||||
if (abc.contains(input[index])) break
|
||||
}
|
||||
|
||||
temp = abcMap[input[index]]!!
|
||||
bytes[listIndex] = bytes[listIndex] or rShift(temp, 2)
|
||||
listIndex++
|
||||
temp = (big3 and temp)
|
||||
if ((temp == big0) && (index == (length - 1))) {
|
||||
return bytes
|
||||
}
|
||||
bytes.add((temp shl 6))
|
||||
for (i in input) {
|
||||
index++
|
||||
if (abc.contains(input[index])) {
|
||||
break
|
||||
}
|
||||
}
|
||||
bytes[listIndex] = bytes[listIndex] or abcMap[input[index]]!!
|
||||
listIndex++
|
||||
}
|
||||
}
|
||||
|
||||
private fun replace(a: String): String {
|
||||
val map = mapOf(
|
||||
'0' to '5',
|
||||
'1' to '6',
|
||||
'2' to '7',
|
||||
'5' to '0',
|
||||
'6' to '1',
|
||||
'7' to '2'
|
||||
)
|
||||
var b = ""
|
||||
a.forEach {
|
||||
b += if (map.containsKey(it)) map[it] else it
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
private fun padLastChars(input:String):String{
|
||||
return if(input.reversed()[3].isDigit()) input
|
||||
else input.dropLast(4)
|
||||
}
|
||||
|
||||
private data class HashResponse(
|
||||
val hash: String? = null,
|
||||
val version:String? = null
|
||||
)
|
||||
|
||||
private data class SetupResponse(
|
||||
val seed: String,
|
||||
val src: String?=null,
|
||||
val link:String?=null
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
import com.lagradost.cloudstream3.SubtitleFile
|
||||
import com.lagradost.cloudstream3.apmap
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.argamap
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.extractorApis
|
||||
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
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.
|
||||
* */
|
||||
class Vidstream(val mainUrl: String) {
|
||||
val name: String = "Vidstream"
|
||||
|
||||
private fun getExtractorUrl(id: String): String {
|
||||
return "$mainUrl/streaming.php?id=$id"
|
||||
}
|
||||
|
||||
private fun getDownloadUrl(id: String): String {
|
||||
return "$mainUrl/download?id=$id"
|
||||
}
|
||||
|
||||
private val normalApis = arrayListOf(MultiQuality())
|
||||
|
||||
// https://gogo-stream.com/streaming.php?id=MTE3NDg5
|
||||
suspend fun getUrl(
|
||||
id: String,
|
||||
isCasting: Boolean = false,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit,
|
||||
): Boolean {
|
||||
val extractorUrl = getExtractorUrl(id)
|
||||
argamap(
|
||||
{
|
||||
normalApis.apmap { api ->
|
||||
val url = api.getExtractorUrl(id)
|
||||
api.getSafeUrl(
|
||||
url,
|
||||
callback = callback,
|
||||
subtitleCallback = subtitleCallback
|
||||
)
|
||||
}
|
||||
}, {
|
||||
/** Stolen from GogoanimeProvider.kt extractor */
|
||||
val link = getDownloadUrl(id)
|
||||
println("Generated vidstream download link: $link")
|
||||
val page = app.get(link, referer = extractorUrl)
|
||||
|
||||
val pageDoc = Jsoup.parse(page.text)
|
||||
val qualityRegex = Regex("(\\d+)P")
|
||||
|
||||
//a[download]
|
||||
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()
|
||||
.toString()
|
||||
|
||||
if (!loadExtractor(href, link, subtitleCallback, callback)) {
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
this.name,
|
||||
name = this.name,
|
||||
href,
|
||||
page.url,
|
||||
getQualityFromName(qual),
|
||||
element.attr("href").contains(".m3u8")
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}, {
|
||||
with(app.get(extractorUrl)) {
|
||||
val document = Jsoup.parse(this.text)
|
||||
val primaryLinks = document.select("ul.list-server-items > li.linkserver")
|
||||
//val extractedLinksList: MutableList<ExtractorLink> = mutableListOf()
|
||||
|
||||
// All vidstream links passed to extractors
|
||||
primaryLinks.distinctBy { it.attr("data-video") }.forEach { element ->
|
||||
val link = element.attr("data-video")
|
||||
//val name = element.text()
|
||||
|
||||
// Matches vidstream links with extractors
|
||||
extractorApis.filter { !it.requiresReferer || !isCasting }.apmap { api ->
|
||||
if (link.startsWith(api.mainUrl)) {
|
||||
api.getSafeUrl(link, extractorUrl, subtitleCallback, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
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.getQualityFromName
|
||||
|
||||
open class VoeExtractor : ExtractorApi() {
|
||||
override val name: String = "Voe"
|
||||
override val mainUrl: String = "https://voe.sx"
|
||||
override val requiresReferer = false
|
||||
|
||||
private data class ResponseLinks(
|
||||
@JsonProperty("hls") val url: String?,
|
||||
@JsonProperty("video_height") val label: Int?
|
||||
//val type: String // Mp4
|
||||
)
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> {
|
||||
val extractedLinksList: MutableList<ExtractorLink> = 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")
|
||||
.trim()
|
||||
//Log.i(this.name, "Result => (src) ${src}")
|
||||
parseJson<ResponseLinks?>(src)?.let { voelink ->
|
||||
//Log.i(this.name, "Result => (voelink) ${voelink}")
|
||||
val linkUrl = voelink.url
|
||||
val linkLabel = voelink.label?.toString() ?: ""
|
||||
if (!linkUrl.isNullOrEmpty()) {
|
||||
extractedLinksList.add(
|
||||
ExtractorLink(
|
||||
name = this.name,
|
||||
source = this.name,
|
||||
url = linkUrl,
|
||||
quality = getQualityFromName(linkLabel),
|
||||
referer = url,
|
||||
isM3u8 = true
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return extractedLinksList
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.network.WebViewResolver
|
||||
import com.lagradost.cloudstream3.utils.ExtractorApi
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8
|
||||
|
||||
open class WatchSB : ExtractorApi() {
|
||||
override var name = "WatchSB"
|
||||
override var mainUrl = "https://watchsb.com"
|
||||
override val requiresReferer = false
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> {
|
||||
val response = app.get(
|
||||
url, interceptor = WebViewResolver(
|
||||
Regex("""master\.m3u8""")
|
||||
)
|
||||
)
|
||||
|
||||
return generateM3u8(name, response.url, url, headers = response.headers.toMap())
|
||||
}
|
||||
}
|
|
@ -1,127 +0,0 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.ErrorLoadingException
|
||||
import com.lagradost.cloudstream3.animeproviders.NineAnimeProvider.Companion.cipher
|
||||
import com.lagradost.cloudstream3.animeproviders.NineAnimeProvider.Companion.encrypt
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.utils.ExtractorApi
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
|
||||
class Vidstreamz : WcoStream() {
|
||||
override var mainUrl = "https://vidstreamz.online"
|
||||
}
|
||||
|
||||
class Vizcloud : WcoStream() {
|
||||
override var mainUrl = "https://vizcloud2.ru"
|
||||
}
|
||||
|
||||
class Vizcloud2 : WcoStream() {
|
||||
override var mainUrl = "https://vizcloud2.online"
|
||||
}
|
||||
|
||||
class VizcloudOnline : WcoStream() {
|
||||
override var mainUrl = "https://vizcloud.online"
|
||||
}
|
||||
|
||||
class VizcloudXyz : WcoStream() {
|
||||
override var mainUrl = "https://vizcloud.xyz"
|
||||
}
|
||||
|
||||
class VizcloudLive : WcoStream() {
|
||||
override var mainUrl = "https://vizcloud.live"
|
||||
}
|
||||
|
||||
class VizcloudInfo : WcoStream() {
|
||||
override var mainUrl = "https://vizcloud.info"
|
||||
}
|
||||
|
||||
class MwvnVizcloudInfo : WcoStream() {
|
||||
override var mainUrl = "https://mwvn.vizcloud.info"
|
||||
}
|
||||
|
||||
class VizcloudDigital : WcoStream() {
|
||||
override var mainUrl = "https://vizcloud.digital"
|
||||
}
|
||||
|
||||
class VizcloudCloud : WcoStream() {
|
||||
override var mainUrl = "https://vizcloud.cloud"
|
||||
}
|
||||
|
||||
class VizcloudSite : WcoStream() {
|
||||
override var mainUrl = "https://vizcloud.site"
|
||||
}
|
||||
|
||||
open class WcoStream : ExtractorApi() {
|
||||
override var name = "VidStream" // Cause works for animekisa and wco
|
||||
override var mainUrl = "https://vidstream.pro"
|
||||
override val requiresReferer = false
|
||||
private val regex = Regex("(.+?/)e(?:mbed)?/([a-zA-Z0-9]+)")
|
||||
|
||||
companion object {
|
||||
// taken from https://github.com/saikou-app/saikou/blob/b35364c8c2a00364178a472fccf1ab72f09815b4/app/src/main/java/ani/saikou/parsers/anime/extractors/VizCloud.kt
|
||||
// GNU General Public License v3.0 https://github.com/saikou-app/saikou/blob/main/LICENSE.md
|
||||
private var lastChecked = 0L
|
||||
private const val jsonLink =
|
||||
"https://raw.githubusercontent.com/chenkaslowankiya/BruhFlow/main/keys.json"
|
||||
private var cipherKey: VizCloudKey? = null
|
||||
suspend fun getKey(): VizCloudKey {
|
||||
cipherKey =
|
||||
if (cipherKey != null && (lastChecked - System.currentTimeMillis()) < 1000 * 60 * 30) cipherKey!!
|
||||
else {
|
||||
lastChecked = System.currentTimeMillis()
|
||||
app.get(jsonLink).parsed()
|
||||
}
|
||||
return cipherKey!!
|
||||
}
|
||||
|
||||
data class VizCloudKey(
|
||||
@JsonProperty("cipherKey") val cipherKey: String,
|
||||
@JsonProperty("mainKey") val mainKey: String,
|
||||
@JsonProperty("encryptKey") val encryptKey: String,
|
||||
@JsonProperty("dashTable") val dashTable: String
|
||||
)
|
||||
|
||||
private const val baseTable =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+=/_"
|
||||
|
||||
private fun dashify(id: String, dashTable: String): String {
|
||||
val table = dashTable.split(" ")
|
||||
return id.mapIndexedNotNull { i, c ->
|
||||
table.getOrNull((baseTable.indexOf(c) * 16) + (i % 16))
|
||||
}.joinToString("-")
|
||||
}
|
||||
}
|
||||
|
||||
//private val key = "LCbu3iYC7ln24K7P" // key credits @Modder4869
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> {
|
||||
val group = regex.find(url)?.groupValues!!
|
||||
|
||||
val host = group[1]
|
||||
val viz = getKey()
|
||||
val id = encrypt(
|
||||
cipher(
|
||||
viz.cipherKey,
|
||||
encrypt(group[2], viz.encryptKey).also { println(it) }
|
||||
).also { println(it) },
|
||||
viz.encryptKey
|
||||
).also { println(it) }
|
||||
|
||||
val link =
|
||||
"${host}mediainfo/${dashify(id, viz.dashTable)}?key=${viz.mainKey}" //
|
||||
val response = app.get(link, referer = referer)
|
||||
|
||||
data class Sources(@JsonProperty("file") val file: String)
|
||||
data class Media(@JsonProperty("sources") val sources: List<Sources>)
|
||||
data class Data(@JsonProperty("media") val media: Media)
|
||||
data class Response(@JsonProperty("data") val data: Data)
|
||||
|
||||
|
||||
if (!response.text.startsWith("{")) throw ErrorLoadingException("Seems like 9Anime kiddies changed stuff again, Go touch some grass for bout an hour Or use a different Server")
|
||||
return response.parsed<Response>().data.media.sources.map {
|
||||
ExtractorLink(name, it.file,it.file,host,Qualities.Unknown.value,it.file.contains(".m3u8"))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.app
|
||||
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 LayarKaca: XStreamCdn() {
|
||||
override val name: String = "LayarKaca-xxi"
|
||||
override val mainUrl: String = "https://layarkacaxxi.icu"
|
||||
}
|
||||
|
||||
class DBfilm: XStreamCdn() {
|
||||
override val name: String = "DBfilm"
|
||||
override val mainUrl: String = "https://dbfilm.bar"
|
||||
}
|
||||
|
||||
class Luxubu : XStreamCdn(){
|
||||
override val name: String = "FE"
|
||||
override val mainUrl: String = "https://www.luxubu.review"
|
||||
}
|
||||
|
||||
class FEmbed: XStreamCdn() {
|
||||
override val name: String = "FEmbed"
|
||||
override val mainUrl: String = "https://www.fembed.com"
|
||||
}
|
||||
|
||||
class Fplayer: XStreamCdn() {
|
||||
override val name: String = "Fplayer"
|
||||
override val mainUrl: String = "https://fplayer.info"
|
||||
}
|
||||
|
||||
class FeHD: XStreamCdn() {
|
||||
override val name: String = "FeHD"
|
||||
override val mainUrl: String = "https://fembed-hd.com"
|
||||
override var domainUrl: String = "fembed-hd.com"
|
||||
}
|
||||
|
||||
open class XStreamCdn : ExtractorApi() {
|
||||
override val name: String = "XStreamCdn"
|
||||
override val mainUrl: String = "https://embedsito.com"
|
||||
override val requiresReferer = false
|
||||
open var domainUrl: String = "embedsito.com"
|
||||
|
||||
private data class ResponseData(
|
||||
@JsonProperty("file") val file: String,
|
||||
@JsonProperty("label") val label: String,
|
||||
//val type: String // Mp4
|
||||
)
|
||||
|
||||
private data class ResponseJson(
|
||||
@JsonProperty("success") val success: Boolean,
|
||||
@JsonProperty("data") val data: List<ResponseData>?
|
||||
)
|
||||
|
||||
override fun getExtractorUrl(id: String): String {
|
||||
return "$domainUrl/api/source/$id"
|
||||
}
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> {
|
||||
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}"
|
||||
val extractedLinksList: MutableList<ExtractorLink> = 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<ResponseJson?>(text)?.let {
|
||||
if (it.success && it.data != null) {
|
||||
it.data.forEach { data ->
|
||||
extractedLinksList.add(
|
||||
ExtractorLink(
|
||||
name,
|
||||
name = name,
|
||||
data.file,
|
||||
url,
|
||||
getQualityFromName(data.label),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return extractedLinksList
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
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.getQualityFromName
|
||||
|
||||
class YourUpload: ExtractorApi() {
|
||||
override val name = "Yourupload"
|
||||
override val mainUrl = "https://www.yourupload.com"
|
||||
override val requiresReferer = false
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> {
|
||||
val sources = mutableListOf<ExtractorLink>()
|
||||
with(app.get(url).document) {
|
||||
val quality = Regex("\\d{3,4}p").find(this.select("title").text())?.groupValues?.get(0)
|
||||
this.select("script").map { script ->
|
||||
if (script.data().contains("var jwplayerOptions = {")) {
|
||||
val data =
|
||||
script.data().substringAfter("var jwplayerOptions = {").substringBefore(",\n")
|
||||
val link = tryParseJson<ResponseSource>(
|
||||
"{${
|
||||
data.replace("file", "\"file\"").replace("'", "\"")
|
||||
}}"
|
||||
)
|
||||
sources.add(
|
||||
ExtractorLink(
|
||||
source = name,
|
||||
name = name,
|
||||
url = link!!.file,
|
||||
referer = url,
|
||||
quality = getQualityFromName(quality)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return sources
|
||||
}
|
||||
|
||||
private data class ResponseSource(
|
||||
@JsonProperty("file") val file: String,
|
||||
)
|
||||
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
import com.lagradost.cloudstream3.SubtitleFile
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.utils.ExtractorApi
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.schemaStripRegex
|
||||
import org.schabi.newpipe.extractor.ServiceList
|
||||
import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor
|
||||
import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeStreamLinkHandlerFactory
|
||||
import org.schabi.newpipe.extractor.stream.SubtitlesStream
|
||||
import org.schabi.newpipe.extractor.stream.VideoStream
|
||||
|
||||
class YoutubeShortLinkExtractor : YoutubeExtractor() {
|
||||
override val mainUrl = "https://youtu.be"
|
||||
|
||||
override fun getExtractorUrl(id: String): String {
|
||||
return "$mainUrl/$id"
|
||||
}
|
||||
}
|
||||
|
||||
class YoutubeMobileExtractor : YoutubeExtractor() {
|
||||
override val mainUrl = "https://m.youtube.com"
|
||||
}
|
||||
class YoutubeNoCookieExtractor : YoutubeExtractor() {
|
||||
override val mainUrl = "https://www.youtube-nocookie.com"
|
||||
}
|
||||
|
||||
open class YoutubeExtractor : ExtractorApi() {
|
||||
override val mainUrl = "https://www.youtube.com"
|
||||
override val requiresReferer = false
|
||||
override val name = "YouTube"
|
||||
|
||||
companion object {
|
||||
private var ytVideos: MutableMap<String, List<VideoStream>> = mutableMapOf()
|
||||
private var ytVideosSubtitles: MutableMap<String, List<SubtitlesStream>> = mutableMapOf()
|
||||
}
|
||||
|
||||
override fun getExtractorUrl(id: String): String {
|
||||
return "$mainUrl/watch?v=$id"
|
||||
}
|
||||
|
||||
override suspend fun getUrl(
|
||||
url: String,
|
||||
referer: String?,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
if (ytVideos[url].isNullOrEmpty()) {
|
||||
val link =
|
||||
YoutubeStreamLinkHandlerFactory.getInstance().fromUrl(
|
||||
url.replace(
|
||||
schemaStripRegex, ""
|
||||
)
|
||||
)
|
||||
|
||||
val s = object : YoutubeStreamExtractor(
|
||||
ServiceList.YouTube,
|
||||
link
|
||||
) {
|
||||
|
||||
}
|
||||
s.fetchPage()
|
||||
ytVideos[url] = s.videoStreams
|
||||
ytVideosSubtitles[url] = try {
|
||||
s.subtitlesDefault.filterNotNull()
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
ytVideos[url]?.mapNotNull {
|
||||
if (it.isVideoOnly || it.height <= 0) return@mapNotNull null
|
||||
|
||||
ExtractorLink(
|
||||
this.name,
|
||||
this.name,
|
||||
it.url ?: return@mapNotNull null,
|
||||
"",
|
||||
it.height
|
||||
)
|
||||
}?.forEach(callback)
|
||||
ytVideosSubtitles[url]?.mapNotNull {
|
||||
SubtitleFile(it.languageTag ?: return@mapNotNull null, it.url ?: return@mapNotNull null)
|
||||
}?.forEach(subtitleCallback)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
package com.lagradost.cloudstream3.extractors
|
||||
|
||||
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
|
||||
import com.lagradost.cloudstream3.utils.getAndUnpack
|
||||
|
||||
class Zplayer: ZplayerV2() {
|
||||
override var name: String = "Zplayer"
|
||||
override var mainUrl: String = "https://zplayer.live"
|
||||
}
|
||||
|
||||
class Upstream: ZplayerV2() {
|
||||
override var name: String = "Upstream" //Here 'cause works
|
||||
override var mainUrl: String = "https://upstream.to"
|
||||
}
|
||||
|
||||
class Streamhub2: ZplayerV2() {
|
||||
override var name = "Streamhub" //Here 'cause works
|
||||
override var mainUrl = "https://streamhub.to"
|
||||
}
|
||||
|
||||
open class ZplayerV2 : ExtractorApi() {
|
||||
override var name = "Zplayer V2"
|
||||
override var mainUrl = "https://v2.zplayer.live"
|
||||
override val requiresReferer = false
|
||||
|
||||
override suspend fun getUrl(url: String, referer: String?): List<ExtractorLink> {
|
||||
val doc = app.get(url).document
|
||||
val sources = mutableListOf<ExtractorLink>()
|
||||
doc.select("script").map { script ->
|
||||
if (script.data().contains("eval(function(p,a,c,k,e,d)")) {
|
||||
val testdata = getAndUnpack(script.data())
|
||||
val m3u8regex = Regex("((https:|http:)\\/\\/.*\\.m3u8)")
|
||||
m3u8regex.findAll(testdata).map {
|
||||
it.value
|
||||
}.toList().apmap { urlm3u8 ->
|
||||
if (urlm3u8.contains("m3u8")) {
|
||||
val testurl = app.get(urlm3u8, headers = mapOf("Referer" to url)).text
|
||||
if (testurl.contains("EXTM3U")) {
|
||||
M3u8Helper.generateM3u8(
|
||||
name,
|
||||
urlm3u8,
|
||||
url,
|
||||
headers = mapOf("Referer" to url)
|
||||
).forEach { link ->
|
||||
sources.add(link)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return sources
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
package com.lagradost.cloudstream3.extractors.helper
|
||||
|
||||
import android.util.Log
|
||||
import com.lagradost.cloudstream3.SubtitleFile
|
||||
import com.lagradost.cloudstream3.apmap
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
|
||||
class AsianEmbedHelper {
|
||||
companion object {
|
||||
suspend fun getUrls(
|
||||
url: String,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
// Fetch links
|
||||
val doc = app.get(url).document
|
||||
val links = doc.select("div#list-server-more > ul > li.linkserver")
|
||||
if (!links.isNullOrEmpty()) {
|
||||
links.apmap {
|
||||
val datavid = it.attr("data-video") ?: ""
|
||||
//Log.i("AsianEmbed", "Result => (datavid) ${datavid}")
|
||||
if (datavid.isNotBlank()) {
|
||||
val res = loadExtractor(datavid, url, subtitleCallback, callback)
|
||||
Log.i("AsianEmbed", "Result => ($res) (datavid) $datavid")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
package com.lagradost.cloudstream3.extractors.helper
|
||||
|
||||
import com.lagradost.cloudstream3.SubtitleFile
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
|
||||
class VstreamhubHelper {
|
||||
companion object {
|
||||
private val baseUrl: String = "https://vstreamhub.com"
|
||||
private val baseName: String = "Vstreamhub"
|
||||
|
||||
suspend fun getUrls(
|
||||
url: String,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
) {
|
||||
if (url.startsWith(baseUrl)) {
|
||||
// Fetch links
|
||||
val doc = app.get(url).document.select("script")
|
||||
doc?.forEach {
|
||||
val innerText = it?.toString()
|
||||
if (!innerText.isNullOrEmpty()) {
|
||||
if (innerText.contains("file:")) {
|
||||
val startString = "file: "
|
||||
val aa = innerText.substring(innerText.indexOf(startString))
|
||||
val linkUrl =
|
||||
aa.substring(startString.length + 1, aa.indexOf("\",")).trim()
|
||||
//Log.i(baseName, "Result => (linkUrl) ${linkUrl}")
|
||||
val exlink = ExtractorLink(
|
||||
name = "$baseName m3u8",
|
||||
source = baseName,
|
||||
url = linkUrl,
|
||||
quality = Qualities.Unknown.value,
|
||||
referer = url,
|
||||
isM3u8 = true
|
||||
)
|
||||
callback.invoke(exlink)
|
||||
}
|
||||
if (innerText.contains("playerInstance")) {
|
||||
val aa =
|
||||
innerText.substring(innerText.indexOf("playerInstance.addButton"))
|
||||
val startString = "window.open(["
|
||||
val bb = aa.substring(aa.indexOf(startString))
|
||||
val datavid = bb.substring(startString.length, bb.indexOf("]"))
|
||||
.removeSurrounding("\"")
|
||||
if (datavid.isNotBlank()) {
|
||||
loadExtractor(datavid, url, subtitleCallback, callback)
|
||||
//Log.i(baseName, "Result => (datavid) ${datavid}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
package com.lagradost.cloudstream3.extractors.helper
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||
import com.lagradost.cloudstream3.app
|
||||
|
||||
class WcoHelper {
|
||||
companion object {
|
||||
private const val BACKUP_KEY_DATA = "github_keys_backup"
|
||||
|
||||
data class ExternalKeys(
|
||||
@JsonProperty("wco_key")
|
||||
val wcoKey: String? = null,
|
||||
@JsonProperty("wco_cipher_key")
|
||||
val wcocipher: String? = null
|
||||
)
|
||||
|
||||
data class NewExternalKeys(
|
||||
@JsonProperty("cipherKey")
|
||||
val cipherkey: String? = null,
|
||||
@JsonProperty("encryptKey")
|
||||
val encryptKey: String? = null,
|
||||
@JsonProperty("mainKey")
|
||||
val mainKey: String? = null,
|
||||
)
|
||||
|
||||
private var keys: ExternalKeys? = null
|
||||
private var newKeys: NewExternalKeys? = null
|
||||
private suspend fun getKeys() {
|
||||
keys = keys
|
||||
?: app.get("https://raw.githubusercontent.com/reduplicated/Cloudstream/master/docs/keys.json")
|
||||
.parsedSafe<ExternalKeys>()?.also { setKey(BACKUP_KEY_DATA, it) } ?: getKey(
|
||||
BACKUP_KEY_DATA
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getWcoKey(): ExternalKeys? {
|
||||
getKeys()
|
||||
return keys
|
||||
}
|
||||
|
||||
private suspend fun getNewKeys() {
|
||||
newKeys = newKeys
|
||||
?: app.get("https://raw.githubusercontent.com/chekaslowakiya/BruhFlow/main/keys.json")
|
||||
.parsedSafe<NewExternalKeys>()?.also { setKey(BACKUP_KEY_DATA, it) } ?: getKey(
|
||||
BACKUP_KEY_DATA
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun getNewWcoKey(): NewExternalKeys? {
|
||||
getNewKeys()
|
||||
return newKeys
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,120 +0,0 @@
|
|||
package com.lagradost.cloudstream3.liveproviders
|
||||
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
import org.jsoup.nodes.Element
|
||||
|
||||
class EjaTv : MainAPI() {
|
||||
override var mainUrl = "https://eja.tv"
|
||||
override var name = "Eja.tv"
|
||||
|
||||
// Universal language?
|
||||
override var lang = "en"
|
||||
override val hasDownloadSupport = false
|
||||
|
||||
override val hasMainPage = true
|
||||
override val supportedTypes = setOf(
|
||||
TvType.Live
|
||||
)
|
||||
|
||||
private fun Element.toSearchResponse(): LiveSearchResponse? {
|
||||
val link = this.select("div.alternative a").last() ?: return null
|
||||
val href = fixUrl(link.attr("href"))
|
||||
val img = this.selectFirst("div.thumb img")
|
||||
val lang = this.selectFirst(".card-title > a")?.attr("href")?.removePrefix("?country=")
|
||||
?.replace("int", "eu") //international -> European Union 🇪🇺
|
||||
return LiveSearchResponse(
|
||||
// Kinda hack way to get the title
|
||||
img?.attr("alt")?.replaceFirst("Watch ", "") ?: return null,
|
||||
href,
|
||||
this@EjaTv.name,
|
||||
TvType.Live,
|
||||
fixUrl(img.attr("src")),
|
||||
lang = lang
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse {
|
||||
// Maybe this based on app language or as setting?
|
||||
val language = "English"
|
||||
val dataMap = mapOf(
|
||||
"News" to mapOf("language" to language, "category" to "News"),
|
||||
"Sports" to mapOf("language" to language, "category" to "Sports"),
|
||||
"Entertainment" to mapOf("language" to language, "category" to "Entertainment")
|
||||
)
|
||||
return HomePageResponse(dataMap.apmap { (title, data) ->
|
||||
val document = app.post(mainUrl, data = data).document
|
||||
val shows = document.select("div.card-body").mapNotNull {
|
||||
it.toSearchResponse()
|
||||
}
|
||||
HomePageList(
|
||||
title,
|
||||
shows,
|
||||
isHorizontalImages = true
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
return app.post(
|
||||
mainUrl, data = mapOf("search" to query)
|
||||
).document.select("div.card-body").mapNotNull {
|
||||
it.toSearchResponse()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val doc = app.get(url).document
|
||||
val sections =
|
||||
doc.select("li.list-group-item.d-flex.justify-content-between.align-items-center")
|
||||
|
||||
val link = fixUrl(sections.last()!!.select("a").attr("href"))
|
||||
|
||||
val title = doc.select("h5.text-center").text()
|
||||
val poster = fixUrl(doc.select("p.text-center img").attr("src"))
|
||||
|
||||
val summary = sections.subList(0, 3).joinToString("<br>") {
|
||||
val innerText = it.ownText().trim()
|
||||
val outerText = it.select("a").text().trim()
|
||||
"$innerText: $outerText"
|
||||
}
|
||||
|
||||
return LiveStreamLoadResponse(
|
||||
title,
|
||||
url,
|
||||
this.name,
|
||||
LoadData(link, title).toJson(),
|
||||
poster,
|
||||
plot = summary
|
||||
)
|
||||
}
|
||||
|
||||
data class LoadData(
|
||||
val url: String,
|
||||
val title: String
|
||||
)
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
val loadData = parseJson<LoadData>(data)
|
||||
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
this.name,
|
||||
loadData.title,
|
||||
loadData.url,
|
||||
"",
|
||||
Qualities.Unknown.value,
|
||||
isM3u8 = true
|
||||
)
|
||||
)
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -1,224 +0,0 @@
|
|||
package com.lagradost.cloudstream3.movieproviders
|
||||
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addActors
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
import org.jsoup.nodes.Element
|
||||
|
||||
class AkwamProvider : MainAPI() {
|
||||
override var lang = "ar"
|
||||
override var mainUrl = "https://akwam.to"
|
||||
override var name = "Akwam"
|
||||
override val usesWebView = false
|
||||
override val hasMainPage = true
|
||||
override val supportedTypes = setOf(TvType.TvSeries, TvType.Movie, TvType.Anime, TvType.Cartoon)
|
||||
|
||||
private fun Element.toSearchResponse(): SearchResponse? {
|
||||
val url = select("a.box").attr("href") ?: return null
|
||||
if (url.contains("/games/") || url.contains("/programs/")) return null
|
||||
val poster = select("picture > img")
|
||||
val title = poster.attr("alt")
|
||||
val posterUrl = poster.attr("data-src")
|
||||
val year = select(".badge-secondary").text().toIntOrNull()
|
||||
|
||||
// If you need to differentiate use the url.
|
||||
return MovieSearchResponse(
|
||||
title,
|
||||
url,
|
||||
this@AkwamProvider.name,
|
||||
TvType.TvSeries,
|
||||
posterUrl,
|
||||
year,
|
||||
null,
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse {
|
||||
// Title, Url
|
||||
val moviesUrl = listOf(
|
||||
"Movies" to "$mainUrl/movies",
|
||||
"Series" to "$mainUrl/series",
|
||||
"Shows" to "$mainUrl/shows"
|
||||
)
|
||||
val pages = moviesUrl.apmap {
|
||||
val doc = app.get(it.second).document
|
||||
val list = doc.select("div.col-lg-auto.col-md-4.col-6.mb-12").mapNotNull { element ->
|
||||
element.toSearchResponse()
|
||||
}
|
||||
HomePageList(it.first, list)
|
||||
}.sortedBy { it.name }
|
||||
return HomePageResponse(pages)
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
val url = "$mainUrl/search?q=$query"
|
||||
val doc = app.get(url).document
|
||||
return doc.select("div.col-lg-auto").mapNotNull {
|
||||
it.toSearchResponse()
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.getIntFromText(): Int? {
|
||||
return Regex("""\d+""").find(this)?.groupValues?.firstOrNull()?.toIntOrNull()
|
||||
}
|
||||
|
||||
private fun Element.toEpisode(): Episode {
|
||||
val a = select("a.text-white")
|
||||
val url = a.attr("href")
|
||||
val title = a.text()
|
||||
val thumbUrl = select("picture > img").attr("src")
|
||||
val date = select("p.entry-date").text()
|
||||
return newEpisode(url) {
|
||||
name = title
|
||||
episode = title.getIntFromText()
|
||||
posterUrl = thumbUrl
|
||||
addDate(date)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val doc = app.get(url).document
|
||||
val isMovie = url.contains("/movie/")
|
||||
val title = doc.select("h1.entry-title").text()
|
||||
val posterUrl = doc.select("picture > img").attr("src")
|
||||
|
||||
val year =
|
||||
doc.select("div.font-size-16.text-white.mt-2").firstOrNull {
|
||||
it.text().contains("السنة")
|
||||
}?.text()?.getIntFromText()
|
||||
|
||||
// A bit iffy to parse twice like this, but it'll do.
|
||||
val duration =
|
||||
doc.select("div.font-size-16.text-white.mt-2").firstOrNull {
|
||||
it.text().contains("مدة الفيلم")
|
||||
}?.text()?.getIntFromText()
|
||||
|
||||
val synopsis = doc.select("div.widget-body p:first-child").text()
|
||||
|
||||
val rating = doc.select("span.mx-2").text().split("/").lastOrNull()?.toRatingInt()
|
||||
|
||||
val tags = doc.select("div.font-size-16.d-flex.align-items-center.mt-3 > a").map {
|
||||
it.text()
|
||||
}
|
||||
|
||||
val actors = doc.select("div.widget-body > div > div.entry-box > a").mapNotNull {
|
||||
val name = it?.selectFirst("div > .entry-title")?.text() ?: return@mapNotNull null
|
||||
val image = it.selectFirst("div > img")?.attr("src") ?: return@mapNotNull null
|
||||
Actor(name, image)
|
||||
}
|
||||
|
||||
val recommendations =
|
||||
doc.select("div > div.widget-body > div.row > div > div.entry-box").mapNotNull {
|
||||
val recTitle = it?.selectFirst("div.entry-body > .entry-title > .text-white")
|
||||
?: return@mapNotNull null
|
||||
val href = recTitle.attr("href") ?: return@mapNotNull null
|
||||
val name = recTitle.text() ?: return@mapNotNull null
|
||||
val poster = it.selectFirst(".entry-image > a > picture > img")?.attr("data-src")
|
||||
?: return@mapNotNull null
|
||||
MovieSearchResponse(name, href, this.name, TvType.Movie, fixUrl(poster))
|
||||
}
|
||||
|
||||
return if (isMovie) {
|
||||
newMovieLoadResponse(
|
||||
title,
|
||||
url,
|
||||
TvType.Movie,
|
||||
url
|
||||
) {
|
||||
this.posterUrl = posterUrl
|
||||
this.year = year
|
||||
this.plot = synopsis
|
||||
this.rating = rating
|
||||
this.tags = tags
|
||||
this.duration = duration
|
||||
this.recommendations = recommendations
|
||||
addActors(actors)
|
||||
}
|
||||
} else {
|
||||
val episodes = doc.select("div.bg-primary2.p-4.col-lg-4.col-md-6.col-12").map {
|
||||
it.toEpisode()
|
||||
}.let {
|
||||
val isReversed = (it.lastOrNull()?.episode ?: 1) < (it.firstOrNull()?.episode ?: 0)
|
||||
if (isReversed)
|
||||
it.reversed()
|
||||
else it
|
||||
}
|
||||
|
||||
newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodes) {
|
||||
this.duration = duration
|
||||
this.posterUrl = posterUrl
|
||||
this.tags = tags.filterNotNull()
|
||||
this.rating = rating
|
||||
this.year = year
|
||||
this.plot = synopsis
|
||||
this.recommendations = recommendations
|
||||
addActors(actors)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// // Maybe possible to not use the url shortener but cba investigating that.
|
||||
// private suspend fun skipUrlShortener(url: String): AppResponse {
|
||||
// return app.get(app.get(url).document.select("a.download-link").attr("href"))
|
||||
// }
|
||||
|
||||
private fun getQualityFromId(id: Int?): Qualities {
|
||||
return when (id) {
|
||||
2 -> Qualities.P360 // Extrapolated
|
||||
3 -> Qualities.P480
|
||||
4 -> Qualities.P720
|
||||
5 -> Qualities.P1080
|
||||
else -> Qualities.Unknown
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
val doc = app.get(data).document
|
||||
|
||||
val links = doc.select("div.tab-content.quality").map { element ->
|
||||
val quality = getQualityFromId(element.attr("id").getIntFromText())
|
||||
element.select(".col-lg-6 > a:contains(تحميل)").map { linkElement ->
|
||||
if (linkElement.attr("href").contains("/download/")) {
|
||||
Pair(
|
||||
linkElement.attr("href"),
|
||||
quality,
|
||||
)
|
||||
} else {
|
||||
val url = "$mainUrl/download${
|
||||
linkElement.attr("href").split("/link")[1]
|
||||
}${data.split("/movie|/episode|/show/episode".toRegex())[1]}"
|
||||
Pair(
|
||||
url,
|
||||
quality,
|
||||
)
|
||||
// just in case if they add the shorts urls again
|
||||
}
|
||||
}
|
||||
}.flatten()
|
||||
|
||||
links.map {
|
||||
val linkDoc = app.get(it.first).document
|
||||
val button = linkDoc.select("div.btn-loader > a")
|
||||
val url = button.attr("href")
|
||||
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
this.name,
|
||||
this.name,
|
||||
url,
|
||||
this.mainUrl,
|
||||
it.second.value
|
||||
)
|
||||
)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -1,206 +0,0 @@
|
|||
package com.lagradost.cloudstream3.movieproviders
|
||||
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addDuration
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
import org.jsoup.Jsoup
|
||||
|
||||
class AllMoviesForYouProvider : MainAPI() {
|
||||
companion object {
|
||||
fun getType(t: String): TvType {
|
||||
return when {
|
||||
t.contains("series") -> TvType.TvSeries
|
||||
t.contains("movies") -> TvType.Movie
|
||||
else -> TvType.Movie
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fetching movies will not work if this link is outdated.
|
||||
override var mainUrl = "https://allmoviesforyou.net"
|
||||
override var name = "AllMoviesForYou"
|
||||
override val hasMainPage = true
|
||||
override val supportedTypes = setOf(
|
||||
TvType.Movie,
|
||||
TvType.TvSeries
|
||||
)
|
||||
|
||||
override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse {
|
||||
val items = ArrayList<HomePageList>()
|
||||
val soup = app.get(mainUrl).document
|
||||
val urls = listOf(
|
||||
Pair("Movies", "section[data-id=movies] article.TPost.B"),
|
||||
Pair("TV Series", "section[data-id=series] article.TPost.B"),
|
||||
)
|
||||
for ((name, element) in urls) {
|
||||
try {
|
||||
val home = soup.select(element).map {
|
||||
val title = it.selectFirst("h2.title")!!.text()
|
||||
val link = it.selectFirst("a")!!.attr("href")
|
||||
TvSeriesSearchResponse(
|
||||
title,
|
||||
link,
|
||||
this.name,
|
||||
TvType.Movie,
|
||||
fixUrl(it.selectFirst("figure img")!!.attr("data-src")),
|
||||
null,
|
||||
null,
|
||||
)
|
||||
}
|
||||
|
||||
items.add(HomePageList(name, home))
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
}
|
||||
}
|
||||
if (items.size <= 0) throw ErrorLoadingException()
|
||||
return HomePageResponse(items)
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
val url = "$mainUrl/?s=$query"
|
||||
val document = app.get(url).document
|
||||
|
||||
val items = document.select("ul.MovieList > li > article > a")
|
||||
return items.map { item ->
|
||||
val href = item.attr("href")
|
||||
val title = item.selectFirst("> h2.Title")!!.text()
|
||||
val img = fixUrl(item.selectFirst("> div.Image > figure > img")!!.attr("data-src"))
|
||||
val type = getType(href)
|
||||
if (type == TvType.Movie) {
|
||||
MovieSearchResponse(title, href, this.name, type, img, null)
|
||||
} else {
|
||||
TvSeriesSearchResponse(
|
||||
title,
|
||||
href,
|
||||
this.name,
|
||||
type,
|
||||
img,
|
||||
null,
|
||||
null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// private fun getLink(document: Document): List<String>? {
|
||||
// val list = ArrayList<String>()
|
||||
// Regex("iframe src=\"(.*?)\"").find(document.html())?.groupValues?.get(1)?.let {
|
||||
// list.add(it)
|
||||
// }
|
||||
// document.select("div.OptionBx")?.forEach { element ->
|
||||
// val baseElement = element.selectFirst("> a.Button")
|
||||
// val elementText = element.selectFirst("> p.AAIco-dns")?.text()
|
||||
// if (elementText == "Streamhub" || elementText == "Dood") {
|
||||
// baseElement?.attr("href")?.let { href ->
|
||||
// list.add(href)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return if (list.isEmpty()) null else list
|
||||
// }
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val type = getType(url)
|
||||
|
||||
val document = app.get(url).document
|
||||
|
||||
val title = document.selectFirst("h1.Title")!!.text()
|
||||
val descipt = document.selectFirst("div.Description > p")!!.text()
|
||||
val rating =
|
||||
document.selectFirst("div.Vote > div.post-ratings > span")?.text()?.toRatingInt()
|
||||
val year = document.selectFirst("span.Date")?.text()
|
||||
val duration = document.selectFirst("span.Time")!!.text()
|
||||
val backgroundPoster =
|
||||
fixUrlNull(document.selectFirst("div.Image > figure > img")?.attr("data-src"))
|
||||
|
||||
if (type == TvType.TvSeries) {
|
||||
val list = ArrayList<Pair<Int, String>>()
|
||||
|
||||
document.select("main > section.SeasonBx > div > div.Title > a").forEach { element ->
|
||||
val season = element.selectFirst("> span")?.text()?.toIntOrNull()
|
||||
val href = element.attr("href")
|
||||
if (season != null && season > 0 && !href.isNullOrBlank()) {
|
||||
list.add(Pair(season, fixUrl(href)))
|
||||
}
|
||||
}
|
||||
if (list.isEmpty()) throw ErrorLoadingException("No Seasons Found")
|
||||
|
||||
val episodeList = ArrayList<Episode>()
|
||||
|
||||
for (season in list) {
|
||||
val seasonResponse = app.get(season.second).text
|
||||
val seasonDocument = Jsoup.parse(seasonResponse)
|
||||
val episodes = seasonDocument.select("table > tbody > tr")
|
||||
if (episodes.isNotEmpty()) {
|
||||
episodes.forEach { episode ->
|
||||
val epNum = episode.selectFirst("> td > span.Num")?.text()?.toIntOrNull()
|
||||
val poster = episode.selectFirst("> td.MvTbImg > a > img")?.attr("data-src")
|
||||
val aName = episode.selectFirst("> td.MvTbTtl > a")
|
||||
val name = aName!!.text()
|
||||
val href = aName.attr("href")
|
||||
val date = episode.selectFirst("> td.MvTbTtl > span")?.text()
|
||||
|
||||
episodeList.add(
|
||||
newEpisode(href) {
|
||||
this.name = name
|
||||
this.season = season.first
|
||||
this.episode = epNum
|
||||
this.posterUrl = fixUrlNull(poster)
|
||||
addDate(date)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return TvSeriesLoadResponse(
|
||||
title,
|
||||
url,
|
||||
this.name,
|
||||
type,
|
||||
episodeList,
|
||||
backgroundPoster,
|
||||
year?.toIntOrNull(),
|
||||
descipt,
|
||||
null,
|
||||
rating
|
||||
)
|
||||
} else {
|
||||
return newMovieLoadResponse(
|
||||
title,
|
||||
url,
|
||||
type,
|
||||
fixUrl(url)
|
||||
) {
|
||||
posterUrl = backgroundPoster
|
||||
this.year = year?.toIntOrNull()
|
||||
this.plot = descipt
|
||||
this.rating = rating
|
||||
addDuration(duration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
val doc = app.get(data).document
|
||||
val iframe = doc.select("body iframe").map { fixUrl(it.attr("src")) }
|
||||
iframe.apmap { id ->
|
||||
if (id.contains("trembed")) {
|
||||
val soup = app.get(id).document
|
||||
soup.select("body iframe").map {
|
||||
val link = fixUrl(it.attr("src").replace("streamhub.to/d/", "streamhub.to/e/"))
|
||||
loadExtractor(link, data, subtitleCallback, callback)
|
||||
}
|
||||
} else loadExtractor(id, data, subtitleCallback, callback)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -1,159 +0,0 @@
|
|||
package com.lagradost.cloudstream3.movieproviders
|
||||
|
||||
import androidx.core.text.parseAsHtml
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
|
||||
|
||||
class AltadefinizioneProvider : MainAPI() {
|
||||
override var lang = "it"
|
||||
override var mainUrl = "https://altadefinizione.tienda"
|
||||
override var name = "Altadefinizione"
|
||||
override val hasMainPage = true
|
||||
override val hasChromecastSupport = true
|
||||
override val supportedTypes = setOf(
|
||||
TvType.Movie
|
||||
)
|
||||
|
||||
override val mainPage = mainPageOf(
|
||||
Pair("$mainUrl/cerca/anno/2022/page/", "Ultimi Film"),
|
||||
Pair("$mainUrl/cerca/openload-quality/HD/page/", "Film in HD"),
|
||||
Pair("$mainUrl/cinema/page/", "Ora al cinema")
|
||||
)
|
||||
|
||||
override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse {
|
||||
val url = request.data + page
|
||||
|
||||
val soup = app.get(url).document
|
||||
val home = soup.select("div.box").map {
|
||||
val title = it.selectFirst("img")!!.attr("alt")
|
||||
val link = it.selectFirst("a")!!.attr("href")
|
||||
val image = mainUrl + it.selectFirst("img")!!.attr("src")
|
||||
val quality = getQualityFromString(it.selectFirst("span")!!.text())
|
||||
|
||||
MovieSearchResponse(
|
||||
title,
|
||||
link,
|
||||
this.name,
|
||||
TvType.Movie,
|
||||
image,
|
||||
null,
|
||||
null,
|
||||
quality,
|
||||
)
|
||||
}
|
||||
return newHomePageResponse(request.name, home)
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
val doc = app.post(
|
||||
"$mainUrl/index.php", data = mapOf(
|
||||
"do" to "search",
|
||||
"subaction" to "search",
|
||||
"story" to query,
|
||||
"sortby" to "news_read"
|
||||
)
|
||||
).document
|
||||
return doc.select("div.box").map {
|
||||
val title = it.selectFirst("img")!!.attr("alt")
|
||||
val link = it.selectFirst("a")!!.attr("href")
|
||||
val image = mainUrl + it.selectFirst("img")!!.attr("src")
|
||||
val quality = getQualityFromString(it.selectFirst("span")!!.text())
|
||||
|
||||
MovieSearchResponse(
|
||||
title,
|
||||
link,
|
||||
this.name,
|
||||
TvType.Movie,
|
||||
image,
|
||||
null,
|
||||
null,
|
||||
quality,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val page = app.get(url)
|
||||
val document = page.document
|
||||
val title = document.selectFirst(" h1 > a")!!.text().replace("streaming", "")
|
||||
val description = document.select("#sfull").toString().substringAfter("altadefinizione")
|
||||
.substringBeforeLast("fonte trama").parseAsHtml().toString()
|
||||
val rating = null
|
||||
|
||||
val year = document.selectFirst("#details > li:nth-child(2)")!!.childNode(2).toString()
|
||||
.filter { it.isDigit() }.toInt()
|
||||
|
||||
val poster = fixUrl(document.selectFirst("div.thumbphoto > img")!!.attr("src"))
|
||||
|
||||
val recomm = document.select("ul.related-list > li").map {
|
||||
val href = it.selectFirst("a")!!.attr("href")
|
||||
val posterUrl = mainUrl + it.selectFirst("img")!!.attr("src")
|
||||
val name = it.selectFirst("img")!!.attr("alt")
|
||||
MovieSearchResponse(
|
||||
name,
|
||||
href,
|
||||
this.name,
|
||||
TvType.Movie,
|
||||
posterUrl,
|
||||
null
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
|
||||
val actors: List<ActorData> =
|
||||
document.select("#staring > a").map {
|
||||
ActorData(actor = Actor(it.text()))
|
||||
}
|
||||
|
||||
val tags: List<String> = document.select("#details > li:nth-child(1) > a").map { it.text() }
|
||||
|
||||
val trailerurl = document.selectFirst("#showtrailer > div > div > iframe")?.attr("src")
|
||||
|
||||
return newMovieLoadResponse(
|
||||
title,
|
||||
url,
|
||||
TvType.Movie,
|
||||
url
|
||||
) {
|
||||
posterUrl = fixUrlNull(poster)
|
||||
this.year = year
|
||||
this.plot = description
|
||||
this.rating = rating
|
||||
this.recommendations = recomm
|
||||
this.duration = null
|
||||
this.actors = actors
|
||||
this.tags = tags
|
||||
addTrailer(trailerurl)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
val doc = app.get(data).document
|
||||
if (doc.select("div.guardahd-player").isNullOrEmpty()) {
|
||||
val videoUrl =
|
||||
doc.select("input").last { it.hasAttr("data-mirror") }.attr("value")
|
||||
loadExtractor(videoUrl, data, subtitleCallback, callback)
|
||||
doc.select("#mirrors > li > a").forEach {
|
||||
loadExtractor(fixUrl(it.attr("data-target")), data, subtitleCallback, callback)
|
||||
}
|
||||
} else {
|
||||
val pagelinks = doc.select("div.guardahd-player").select("iframe").attr("src")
|
||||
val docLinks = app.get(pagelinks).document
|
||||
docLinks.select("body > div > ul > li").forEach {
|
||||
loadExtractor(fixUrl(it.attr("data-link")), data, subtitleCallback, callback)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -1,182 +0,0 @@
|
|||
package com.lagradost.cloudstream3.movieproviders
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.fasterxml.jackson.core.JsonParser
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.animeproviders.GogoanimeProvider.Companion.getStatus
|
||||
import com.lagradost.cloudstream3.utils.DataStore.toKotlinObject
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.getQualityFromName
|
||||
import java.net.URI
|
||||
|
||||
class AsiaFlixProvider : MainAPI() {
|
||||
override var mainUrl = "https://asiaflix.app"
|
||||
override var name = "AsiaFlix"
|
||||
override val hasQuickSearch = false
|
||||
override val hasMainPage = true
|
||||
override val hasChromecastSupport = false
|
||||
override val supportedTypes = setOf(TvType.AsianDrama)
|
||||
|
||||
private val apiUrl = "https://api.asiaflix.app/api/v2"
|
||||
|
||||
data class DashBoardObject(
|
||||
@JsonProperty("sectionName") val sectionName: String,
|
||||
@JsonProperty("type") val type: String?,
|
||||
@JsonProperty("data") val data: List<Data>?
|
||||
)
|
||||
|
||||
data class Episodes(
|
||||
@JsonProperty("_id") val _id: String,
|
||||
@JsonProperty("epUrl") val epUrl: String?,
|
||||
@JsonProperty("number") val number: Int?,
|
||||
@JsonProperty("type") val type: String?,
|
||||
@JsonProperty("extracted") val extracted: String?,
|
||||
@JsonProperty("videoUrl") val videoUrl: String?
|
||||
)
|
||||
|
||||
|
||||
data class Data(
|
||||
@JsonProperty("_id") val _id: String,
|
||||
@JsonProperty("name") val name: String,
|
||||
@JsonProperty("altNames") val altNames: String?,
|
||||
@JsonProperty("image") val image: String?,
|
||||
@JsonProperty("tvStatus") val tvStatus: String?,
|
||||
@JsonProperty("genre") val genre: String?,
|
||||
@JsonProperty("releaseYear") val releaseYear: Int?,
|
||||
@JsonProperty("createdAt") val createdAt: Long?,
|
||||
@JsonProperty("episodes") val episodes: List<Episodes>?,
|
||||
@JsonProperty("views") val views: Int?
|
||||
)
|
||||
|
||||
|
||||
data class DramaPage(
|
||||
@JsonProperty("_id") val _id: String,
|
||||
@JsonProperty("name") val name: String,
|
||||
@JsonProperty("altNames") val altNames: String?,
|
||||
@JsonProperty("synopsis") val synopsis: String?,
|
||||
@JsonProperty("image") val image: String?,
|
||||
@JsonProperty("language") val language: String?,
|
||||
@JsonProperty("dramaUrl") val dramaUrl: String?,
|
||||
@JsonProperty("published") val published: Boolean?,
|
||||
@JsonProperty("tvStatus") val tvStatus: String?,
|
||||
@JsonProperty("firstAirDate") val firstAirDate: String?,
|
||||
@JsonProperty("genre") val genre: String?,
|
||||
@JsonProperty("releaseYear") val releaseYear: Int?,
|
||||
@JsonProperty("createdAt") val createdAt: Long?,
|
||||
@JsonProperty("modifiedAt") val modifiedAt: Long?,
|
||||
@JsonProperty("episodes") val episodes: List<Episodes>,
|
||||
@JsonProperty("__v") val __v: Int?,
|
||||
@JsonProperty("cdnImage") val cdnImage: String?,
|
||||
@JsonProperty("views") val views: Int?
|
||||
)
|
||||
|
||||
private fun Data.toSearchResponse(): TvSeriesSearchResponse {
|
||||
return TvSeriesSearchResponse(
|
||||
name,
|
||||
_id,
|
||||
this@AsiaFlixProvider.name,
|
||||
TvType.AsianDrama,
|
||||
image,
|
||||
releaseYear,
|
||||
episodes?.size,
|
||||
)
|
||||
}
|
||||
|
||||
private fun Episodes.toEpisode(): Episode? {
|
||||
if (videoUrl != null && videoUrl.contains("watch/null") || number == null) return null
|
||||
return videoUrl?.let {
|
||||
Episode(
|
||||
it,
|
||||
null,
|
||||
number,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun DramaPage.toLoadResponse(): TvSeriesLoadResponse {
|
||||
return TvSeriesLoadResponse(
|
||||
name,
|
||||
"$mainUrl$dramaUrl/$_id".replace("drama-detail", "show-details"),
|
||||
this@AsiaFlixProvider.name,
|
||||
TvType.AsianDrama,
|
||||
episodes.mapNotNull { it.toEpisode() }.sortedBy { it.episode },
|
||||
image,
|
||||
releaseYear,
|
||||
synopsis,
|
||||
getStatus(tvStatus ?: ""),
|
||||
null,
|
||||
genre?.split(",")?.map { it.trim() }
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse {
|
||||
val headers = mapOf("X-Requested-By" to "asiaflix-web")
|
||||
val response = app.get("$apiUrl/dashboard", headers = headers).text
|
||||
|
||||
val customMapper =
|
||||
mapper.copy().configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true)
|
||||
// Hack, because it can either be object or a list
|
||||
val cleanedResponse = Regex(""""data":(\{.*?),\{"sectionName"""").replace(response) {
|
||||
""""data":null},{"sectionName""""
|
||||
}
|
||||
|
||||
val dashBoard = customMapper.readValue<List<DashBoardObject>?>(cleanedResponse)
|
||||
|
||||
val listItems = dashBoard?.mapNotNull {
|
||||
it.data?.map { data ->
|
||||
data.toSearchResponse()
|
||||
}?.let { searchResponse ->
|
||||
HomePageList(it.sectionName, searchResponse)
|
||||
}
|
||||
}
|
||||
return HomePageResponse(listItems ?: listOf())
|
||||
}
|
||||
|
||||
data class Link(
|
||||
@JsonProperty("url") val url: String?,
|
||||
)
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
if (isCasting) return false
|
||||
val headers = mapOf("X-Requested-By" to "asiaflix-web")
|
||||
app.get(
|
||||
"$apiUrl/utility/get-stream-links?url=$data",
|
||||
headers = headers
|
||||
).text.toKotlinObject<Link>().url?.let {
|
||||
// val fixedUrl = "https://api.asiaflix.app/api/v2/utility/cors-proxy/playlist/${URLEncoder.encode(it, StandardCharsets.UTF_8.toString())}"
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
name,
|
||||
name,
|
||||
it,
|
||||
"https://asianload1.com/",
|
||||
/** <------ This provider should be added instead */
|
||||
getQualityFromName(it),
|
||||
URI(it).path.endsWith(".m3u8")
|
||||
)
|
||||
)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse>? {
|
||||
val headers = mapOf("X-Requested-By" to "asiaflix-web")
|
||||
val url = "$apiUrl/drama/search?q=$query"
|
||||
val response = app.get(url, headers = headers).text
|
||||
return mapper.readValue<List<Data>?>(response)?.map { it.toSearchResponse() }
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val headers = mapOf("X-Requested-By" to "asiaflix-web")
|
||||
val requestUrl = "$apiUrl/drama?id=${url.split("/").lastOrNull()}"
|
||||
val response = app.get(requestUrl, headers = headers).text
|
||||
val dramaPage = response.toKotlinObject<DramaPage>()
|
||||
return dramaPage.toLoadResponse()
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
package com.lagradost.cloudstream3.movieproviders
|
||||
|
||||
import com.lagradost.cloudstream3.TvType
|
||||
|
||||
/** Needs to inherit from MainAPI() to
|
||||
* make the app know what functions to call
|
||||
*/
|
||||
class AsianLoadProvider : VidstreamProviderTemplate() {
|
||||
override var name = "AsianLoad"
|
||||
override var mainUrl = "https://asianembed.io"
|
||||
override val homePageUrlList = listOf(
|
||||
mainUrl,
|
||||
"$mainUrl/recently-added-raw",
|
||||
"$mainUrl/movies",
|
||||
"$mainUrl/kshow",
|
||||
"$mainUrl/popular",
|
||||
"$mainUrl/ongoing-series"
|
||||
)
|
||||
|
||||
override val iv = "9262859232435825"
|
||||
override val secretKey = "93422192433952489752342908585752"
|
||||
override val secretDecryptKey = secretKey
|
||||
|
||||
override val supportedTypes = setOf(TvType.AsianDrama)
|
||||
}
|
|
@ -1,300 +0,0 @@
|
|||
package com.lagradost.cloudstream3.movieproviders
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.animeproviders.NineAnimeProvider.Companion.decodeVrf
|
||||
import com.lagradost.cloudstream3.animeproviders.NineAnimeProvider.Companion.encode
|
||||
import com.lagradost.cloudstream3.animeproviders.NineAnimeProvider.Companion.encodeVrf
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
import org.jsoup.Jsoup
|
||||
|
||||
open class BflixProvider : MainAPI() {
|
||||
override var mainUrl = "https://bflix.ru"
|
||||
override var name = "Bflix"
|
||||
override val hasMainPage = true
|
||||
override val hasChromecastSupport = true
|
||||
override val hasDownloadSupport = true
|
||||
override val supportedTypes = setOf(
|
||||
TvType.Movie,
|
||||
TvType.TvSeries,
|
||||
)
|
||||
|
||||
//override val uniqueId: Int by lazy { "BflixProvider".hashCode() }
|
||||
|
||||
override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse {
|
||||
val items = ArrayList<HomePageList>()
|
||||
val soup = app.get("$mainUrl/home").document
|
||||
val testa = listOf(
|
||||
Pair("Movies", "div.tab-content[data-name=movies] div.filmlist div.item"),
|
||||
Pair("Shows", "div.tab-content[data-name=shows] div.filmlist div.item"),
|
||||
Pair("Trending", "div.tab-content[data-name=trending] div.filmlist div.item"),
|
||||
Pair(
|
||||
"Latest Movies",
|
||||
"div.container section.bl:contains(Latest Movies) div.filmlist div.item"
|
||||
),
|
||||
Pair(
|
||||
"Latest TV-Series",
|
||||
"div.container section.bl:contains(Latest TV-Series) div.filmlist div.item"
|
||||
),
|
||||
)
|
||||
for ((name, element) in testa) try {
|
||||
val test = soup.select(element).map {
|
||||
val title = it.selectFirst("h3 a")!!.text()
|
||||
val link = fixUrl(it.selectFirst("a")!!.attr("href"))
|
||||
val qualityInfo = it.selectFirst("div.quality")!!.text()
|
||||
val quality = getQualityFromString(qualityInfo)
|
||||
TvSeriesSearchResponse(
|
||||
title,
|
||||
link,
|
||||
this.name,
|
||||
if (link.contains("/movie/")) TvType.Movie else TvType.TvSeries,
|
||||
it.selectFirst("a.poster img")!!.attr("src"),
|
||||
null,
|
||||
null,
|
||||
quality = quality
|
||||
)
|
||||
}
|
||||
items.add(HomePageList(name, test))
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
if (items.size <= 0) throw ErrorLoadingException()
|
||||
return HomePageResponse(items)
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse>? {
|
||||
val encodedquery = encodeVrf(query, mainKey)
|
||||
val url = "$mainUrl/search?keyword=$query&vrf=$encodedquery"
|
||||
val html = app.get(url).text
|
||||
val document = Jsoup.parse(html)
|
||||
|
||||
return document.select(".filmlist div.item").map {
|
||||
val title = it.selectFirst("h3 a")!!.text()
|
||||
val href = fixUrl(it.selectFirst("a")!!.attr("href"))
|
||||
val image = it.selectFirst("a.poster img")!!.attr("src")
|
||||
val isMovie = href.contains("/movie/")
|
||||
val qualityInfo = it.selectFirst("div.quality")!!.text()
|
||||
val quality = getQualityFromString(qualityInfo)
|
||||
|
||||
if (isMovie) {
|
||||
MovieSearchResponse(
|
||||
title,
|
||||
href,
|
||||
this.name,
|
||||
TvType.Movie,
|
||||
image,
|
||||
null,
|
||||
quality = quality
|
||||
)
|
||||
} else {
|
||||
TvSeriesSearchResponse(
|
||||
title,
|
||||
href,
|
||||
this.name,
|
||||
TvType.TvSeries,
|
||||
image,
|
||||
null,
|
||||
null,
|
||||
quality = quality
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class Response(
|
||||
@JsonProperty("html") val html: String
|
||||
)
|
||||
|
||||
companion object {
|
||||
val mainKey = "OrAimkpzm6phmN3j"
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse? {
|
||||
val soup = app.get(url).document
|
||||
val movieid = soup.selectFirst("div#watch")!!.attr("data-id")
|
||||
val movieidencoded = encodeVrf(movieid, mainKey)
|
||||
val title = soup.selectFirst("div.info h1")!!.text()
|
||||
val description = soup.selectFirst(".info .desc")?.text()?.trim()
|
||||
val poster: String? = try {
|
||||
soup.selectFirst("img.poster")!!.attr("src")
|
||||
} catch (e: Exception) {
|
||||
soup.selectFirst(".info .poster img")!!.attr("src")
|
||||
}
|
||||
|
||||
val tags = soup.select("div.info .meta div:contains(Genre) a").map { it.text() }
|
||||
val vrfUrl = "$mainUrl/ajax/film/servers?id=$movieid&vrf=$movieidencoded"
|
||||
println("VRF___ $vrfUrl")
|
||||
val episodes = Jsoup.parse(
|
||||
app.get(
|
||||
vrfUrl
|
||||
).parsed<Response>().html
|
||||
).select("div.episode").map {
|
||||
val a = it.selectFirst("a")
|
||||
val href = fixUrl(a!!.attr("href"))
|
||||
val extraData = a.attr("data-kname").let { str ->
|
||||
str.split("-").mapNotNull { subStr -> subStr.toIntOrNull() }
|
||||
}
|
||||
val isValid = extraData.size == 2
|
||||
val episode = if (isValid) extraData.getOrNull(1) else null
|
||||
val season = if (isValid) extraData.getOrNull(0) else null
|
||||
|
||||
val eptitle = it.selectFirst(".episode a span.name")!!.text()
|
||||
val secondtitle = it.selectFirst(".episode a span")!!.text()
|
||||
.replace(Regex("(Episode (\\d+):|Episode (\\d+)-|Episode (\\d+))"), "") ?: ""
|
||||
Episode(
|
||||
href,
|
||||
secondtitle + eptitle,
|
||||
season,
|
||||
episode,
|
||||
)
|
||||
}
|
||||
val tvType =
|
||||
if (url.contains("/movie/") && episodes.size == 1) TvType.Movie else TvType.TvSeries
|
||||
val recommendations =
|
||||
soup.select("div.bl-2 section.bl div.content div.filmlist div.item")
|
||||
.mapNotNull { element ->
|
||||
val recTitle = element.select("h3 a").text() ?: return@mapNotNull null
|
||||
val image = element.select("a.poster img")?.attr("src")
|
||||
val recUrl = fixUrl(element.select("a").attr("href"))
|
||||
MovieSearchResponse(
|
||||
recTitle,
|
||||
recUrl,
|
||||
this.name,
|
||||
if (recUrl.contains("/movie/")) TvType.Movie else TvType.TvSeries,
|
||||
image,
|
||||
year = null
|
||||
)
|
||||
}
|
||||
val rating = soup.selectFirst(".info span.imdb")?.text()?.toRatingInt()
|
||||
val durationdoc = soup.selectFirst("div.info div.meta").toString()
|
||||
val durationregex = Regex("((\\d+) min)")
|
||||
val yearegex = Regex("<span>(\\d+)</span>")
|
||||
val duration = if (durationdoc.contains("na min")) null
|
||||
else durationregex.find(durationdoc)?.destructured?.component1()?.replace(" min", "")
|
||||
?.toIntOrNull()
|
||||
val year = if (mainUrl == "https://bflix.ru") {
|
||||
yearegex.find(durationdoc)?.destructured?.component1()
|
||||
?.replace(Regex("<span>|</span>"), "")
|
||||
} else null
|
||||
return when (tvType) {
|
||||
TvType.TvSeries -> {
|
||||
TvSeriesLoadResponse(
|
||||
title,
|
||||
url,
|
||||
this.name,
|
||||
tvType,
|
||||
episodes,
|
||||
poster,
|
||||
year?.toIntOrNull(),
|
||||
description,
|
||||
null,
|
||||
rating,
|
||||
tags,
|
||||
recommendations = recommendations,
|
||||
duration = duration,
|
||||
)
|
||||
}
|
||||
TvType.Movie -> {
|
||||
MovieLoadResponse(
|
||||
title,
|
||||
url,
|
||||
this.name,
|
||||
tvType,
|
||||
url,
|
||||
poster,
|
||||
year?.toIntOrNull(),
|
||||
description,
|
||||
rating,
|
||||
tags,
|
||||
recommendations = recommendations,
|
||||
duration = duration
|
||||
)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data class Subtitles(
|
||||
@JsonProperty("file") val file: String,
|
||||
@JsonProperty("label") val label: String,
|
||||
@JsonProperty("kind") val kind: String
|
||||
)
|
||||
|
||||
data class Links(
|
||||
@JsonProperty("url") val url: String
|
||||
)
|
||||
|
||||
data class Servers(
|
||||
@JsonProperty("28") val mcloud: String?,
|
||||
@JsonProperty("35") val mp4upload: String?,
|
||||
@JsonProperty("40") val streamtape: String?,
|
||||
@JsonProperty("41") val vidstream: String?,
|
||||
@JsonProperty("43") val videovard: String?
|
||||
)
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
val soup = app.get(data).document
|
||||
|
||||
val movieid = encode(soup.selectFirst("div#watch")?.attr("data-id") ?: return false)
|
||||
val movieidencoded = encodeVrf(movieid, mainKey)
|
||||
Jsoup.parse(
|
||||
parseJson<Response>(
|
||||
app.get(
|
||||
"$mainUrl/ajax/film/servers?id=$movieid&vrf=$movieidencoded"
|
||||
).text
|
||||
).html
|
||||
)
|
||||
.select("html body #episodes").map {
|
||||
val cleandata = data.replace(mainUrl, "")
|
||||
val a = it.select("a").map {
|
||||
it.attr("data-kname")
|
||||
}
|
||||
val tvType =
|
||||
if (data.contains("movie/") && a.size == 1) TvType.Movie else TvType.TvSeries
|
||||
val servers = if (tvType == TvType.Movie) it.select(".episode a").attr("data-ep")
|
||||
else
|
||||
it.select(".episode a[href=$cleandata]").attr("data-ep")
|
||||
?: it.select(".episode a[href=${cleandata.replace("/1-full", "")}]")
|
||||
.attr("data-ep")
|
||||
val jsonservers = parseJson<Servers?>(servers) ?: return@map
|
||||
listOfNotNull(
|
||||
jsonservers.vidstream,
|
||||
jsonservers.mcloud,
|
||||
jsonservers.mp4upload,
|
||||
jsonservers.streamtape,
|
||||
jsonservers.videovard,
|
||||
).mapNotNull {
|
||||
val epserver = app.get("$mainUrl/ajax/episode/info?id=$it").text
|
||||
(if (epserver.contains("url")) {
|
||||
parseJson<Links>(epserver)
|
||||
} else null)?.url?.let {
|
||||
decodeVrf(it, mainKey)
|
||||
}
|
||||
}.apmap { url ->
|
||||
loadExtractor(
|
||||
url, data, subtitleCallback, callback
|
||||
)
|
||||
}
|
||||
//Apparently any server works, I haven't found any diference
|
||||
val sublink =
|
||||
app.get("$mainUrl/ajax/episode/subtitles/${jsonservers.mcloud}").text
|
||||
val jsonsub = parseJson<List<Subtitles>>(sublink)
|
||||
jsonsub.forEach { subtitle ->
|
||||
subtitleCallback(
|
||||
SubtitleFile(subtitle.label, subtitle.file)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -1,163 +0,0 @@
|
|||
package com.lagradost.cloudstream3.movieproviders
|
||||
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.LoadResponse.Companion.addTrailer
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
import org.jsoup.nodes.Element
|
||||
|
||||
class CimaNowProvider : MainAPI() {
|
||||
override var lang = "ar"
|
||||
override var mainUrl = "https://cimanow.cc"
|
||||
override var name = "CimaNow"
|
||||
override val usesWebView = false
|
||||
override val hasMainPage = true
|
||||
override val supportedTypes = setOf(TvType.TvSeries, TvType.Movie)
|
||||
|
||||
private fun String.getIntFromText(): Int? {
|
||||
return Regex("""\d+""").find(this)?.groupValues?.firstOrNull()?.toIntOrNull()
|
||||
}
|
||||
|
||||
private fun Element.toSearchResponse(): SearchResponse? {
|
||||
val url = this.attr("href")
|
||||
val posterUrl = select("img")?.attr("data-src")
|
||||
var title = select("li[aria-label=\"title\"]").html().replace(" <em>.*|\\\\n".toRegex(), "").replace(" ", "")
|
||||
val year = select("li[aria-label=\"year\"]").text().toIntOrNull()
|
||||
val tvType = if (url.contains("فيلم|مسرحية|حفلات".toRegex())) TvType.Movie else TvType.TvSeries
|
||||
val quality = select("li[aria-label=\"ribbon\"]").first()?.text()?.replace(" |-|1080|720".toRegex(), "")
|
||||
val dubEl = select("li[aria-label=\"ribbon\"]:nth-child(2)").isNotEmpty()
|
||||
val dubStatus = if(dubEl) select("li[aria-label=\"ribbon\"]:nth-child(2)").text().contains("مدبلج")
|
||||
else select("li[aria-label=\"ribbon\"]:nth-child(1)").text().contains("مدبلج")
|
||||
if(dubStatus) title = "$title (مدبلج)"
|
||||
return MovieSearchResponse(
|
||||
"$title ${select("li[aria-label=\"ribbon\"]:contains(الموسم)").text()}",
|
||||
url,
|
||||
this@CimaNowProvider.name,
|
||||
tvType,
|
||||
posterUrl,
|
||||
year,
|
||||
null,
|
||||
quality = getQualityFromString(quality)
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getMainPage(page: Int, request : MainPageRequest): HomePageResponse {
|
||||
|
||||
val doc = app.get("$mainUrl/home", headers = mapOf("user-agent" to "MONKE")).document
|
||||
val pages = doc.select("section").not("section:contains(أختر وجهتك المفضلة)").not("section:contains(تم اضافته حديثاً)").apmap {
|
||||
val name = it.select("span").html().replace("<em>.*| <i c.*".toRegex(), "")
|
||||
val list = it.select("a").mapNotNull {
|
||||
if(it.attr("href").contains("$mainUrl/category/|$mainUrl/الاكثر-مشاهدة/".toRegex())) return@mapNotNull null
|
||||
it.toSearchResponse()
|
||||
}
|
||||
HomePageList(name, list)
|
||||
}
|
||||
return HomePageResponse(pages)
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
val result = arrayListOf<SearchResponse>()
|
||||
val doc = app.get("$mainUrl/page/1/?s=$query").document
|
||||
val paginationElement = doc.select("ul[aria-label=\"pagination\"]")
|
||||
doc.select("section article a").map {
|
||||
val postUrl = it.attr("href")
|
||||
if(it.select("li[aria-label=\"episode\"]").isNotEmpty()) return@map
|
||||
if(postUrl.contains("$mainUrl/expired-download/|$mainUrl/افلام-اون-لاين/".toRegex())) return@map
|
||||
result.add(it.toSearchResponse()!!)
|
||||
}
|
||||
if(paginationElement.isNotEmpty()) {
|
||||
val max = paginationElement.select("li").not("li.active").last()?.text()?.toIntOrNull()
|
||||
if (max != null) {
|
||||
if(max > 5) return result.distinct().sortedBy { it.name }
|
||||
(2..max!!).toList().apmap {
|
||||
app.get("$mainUrl/page/$it/?s=$query\"").document.select("section article a").map { element ->
|
||||
val postUrl = element.attr("href")
|
||||
if(element.select("li[aria-label=\"episode\"]").isNotEmpty()) return@map
|
||||
if(postUrl.contains("$mainUrl/expired-download/|$mainUrl/افلام-اون-لاين/".toRegex())) return@map
|
||||
result.add(element.toSearchResponse()!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.distinct().sortedBy { it.name }
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val doc = app.get(url).document
|
||||
val posterUrl = doc.select("body > script:nth-child(3)").html().replace(".*,\"image\":\"|\".*".toRegex(),"").ifEmpty { doc.select("meta[property=\"og:image\"]").attr("content") }
|
||||
val year = doc.select("article ul:nth-child(1) li a").last()?.text()?.toIntOrNull()
|
||||
val title = doc.select("title").text().split(" | ")[0]
|
||||
val isMovie = title.contains("فيلم|حفلات|مسرحية".toRegex())
|
||||
val youtubeTrailer = doc.select("iframe")?.attr("src")
|
||||
|
||||
val synopsis = doc.select("ul#details li:contains(لمحة) p").text()
|
||||
|
||||
val tags = doc.select("article ul").first()?.select("li")?.map { it.text() }
|
||||
|
||||
val recommendations = doc.select("ul#related li").map { element ->
|
||||
MovieSearchResponse(
|
||||
apiName = this@CimaNowProvider.name,
|
||||
url = element.select("a").attr("href"),
|
||||
name = element.select("img:nth-child(2)").attr("alt"),
|
||||
posterUrl = element.select("img:nth-child(2)").attr("src")
|
||||
)
|
||||
}
|
||||
|
||||
return if (isMovie) {
|
||||
newMovieLoadResponse(
|
||||
title,
|
||||
url,
|
||||
TvType.Movie,
|
||||
"$url/watching"
|
||||
) {
|
||||
this.posterUrl = posterUrl
|
||||
this.year = year
|
||||
this.recommendations = recommendations
|
||||
this.plot = synopsis
|
||||
this.tags = tags
|
||||
addTrailer(youtubeTrailer)
|
||||
}
|
||||
} else {
|
||||
val episodes = doc.select("ul#eps li").map { episode ->
|
||||
Episode(
|
||||
episode.select("a").attr("href")+"/watching",
|
||||
episode.select("a img:nth-child(2)").attr("alt"),
|
||||
doc.select("span[aria-label=\"season-title\"]").html().replace("<p>.*|\n".toRegex(), "").getIntFromText(),
|
||||
episode.select("a em").text().toIntOrNull(),
|
||||
episode.select("a img:nth-child(2)").attr("src")
|
||||
)
|
||||
}
|
||||
newTvSeriesLoadResponse(title, url, TvType.TvSeries, episodes.distinct().sortedBy { it.episode }) {
|
||||
this.posterUrl = posterUrl
|
||||
this.tags = tags
|
||||
this.year = year
|
||||
this.plot = synopsis
|
||||
this.recommendations = recommendations
|
||||
addTrailer(youtubeTrailer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
app.get("$data").document.select("ul#download [aria-label=\"quality\"]").forEach {
|
||||
val name = if(it.select("span").text().contains("فائق السرعة")) "Fast Servers" else "Servers"
|
||||
it.select("a").forEach { media ->
|
||||
callback.invoke(
|
||||
ExtractorLink(
|
||||
source = this.name,
|
||||
name = name,
|
||||
url = media.attr("href"),
|
||||
referer = this.mainUrl,
|
||||
quality = media.text().getIntFromText() ?: Qualities.Unknown.value
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -1,188 +0,0 @@
|
|||
package com.lagradost.cloudstream3.movieproviders
|
||||
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
|
||||
|
||||
class CineblogProvider : MainAPI() {
|
||||
override var lang = "it"
|
||||
override var mainUrl = "https://cb01.rip"
|
||||
override var name = "CineBlog"
|
||||
override val hasMainPage = true
|
||||
override val hasChromecastSupport = true
|
||||
override val supportedTypes = setOf(
|
||||
TvType.Movie,
|
||||
TvType.TvSeries,
|
||||
)
|
||||
override val mainPage = mainPageOf(
|
||||
Pair("$mainUrl/popolari/page/number/?get=movies", "Film Popolari"),
|
||||
Pair("$mainUrl/popolari/page/number/?get=tv", "Serie Tv Popolari"),
|
||||
Pair("$mainUrl/i-piu-votati/page/number/?get=movies", "Film più votati"),
|
||||
Pair("$mainUrl/i-piu-votati/page/number/?get=tv", "Serie Tv più votate"),
|
||||
Pair("$mainUrl/anno/2022/page/number", "Ultime uscite"),
|
||||
)
|
||||
|
||||
override suspend fun getMainPage(
|
||||
page: Int,
|
||||
request : MainPageRequest
|
||||
): HomePageResponse {
|
||||
val url = request.data.replace("number", page.toString())
|
||||
val soup = app.get(url, referer = url.substringBefore("page")).document
|
||||
val home = soup.select("article.item").map {
|
||||
val title = it.selectFirst("div.data > h3 > a")!!.text().substringBefore("(")
|
||||
val link = it.selectFirst("div.poster > a")!!.attr("href")
|
||||
val quality = getQualityFromString(it.selectFirst("span.quality")?.text())
|
||||
TvSeriesSearchResponse(
|
||||
title,
|
||||
link,
|
||||
this.name,
|
||||
TvType.Movie,
|
||||
it.selectFirst("img")!!.attr("src"),
|
||||
null,
|
||||
null,
|
||||
quality = quality
|
||||
)
|
||||
}
|
||||
return newHomePageResponse(request.name, home)
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
val queryformatted = query.replace(" ", "+")
|
||||
val url = "$mainUrl?s=$queryformatted"
|
||||
val doc = app.get(url,referer= mainUrl ).document
|
||||
return doc.select("div.result-item").map {
|
||||
val href = it.selectFirst("div.image > div > a")!!.attr("href")
|
||||
val poster = it.selectFirst("div.image > div > a > img")!!.attr("src")
|
||||
val name = it.selectFirst("div.details > div.title > a")!!.text().substringBefore("(")
|
||||
MovieSearchResponse(
|
||||
name,
|
||||
href,
|
||||
this.name,
|
||||
TvType.Movie,
|
||||
poster
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun load(url: String): LoadResponse {
|
||||
val page = app.get(url)
|
||||
val document = page.document
|
||||
val type = if (url.contains("film")) TvType.Movie else TvType.TvSeries
|
||||
val title = document.selectFirst("div.data > h1")!!.text().substringBefore("(")
|
||||
val description = document.select("#info > div.wp-content > p").html().toString()
|
||||
val rating = null
|
||||
var year = document.selectFirst(" div.data > div.extra > span.date")!!.text().substringAfter(",")
|
||||
.filter { it.isDigit() }
|
||||
if (year.length > 4) {
|
||||
year = year.dropLast(4)
|
||||
}
|
||||
|
||||
val poster = document.selectFirst("div.poster > img")!!.attr("src")
|
||||
|
||||
val recomm = document.select("#single_relacionados >article").map {
|
||||
val href = it.selectFirst("a")!!.attr("href")
|
||||
val posterUrl = it.selectFirst("a > img")!!.attr("src")
|
||||
val name = it.selectFirst("a > img")!!.attr("alt").substringBeforeLast("(")
|
||||
MovieSearchResponse(
|
||||
name,
|
||||
href,
|
||||
this.name,
|
||||
TvType.Movie,
|
||||
posterUrl
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (type == TvType.TvSeries) {
|
||||
|
||||
val episodeList = ArrayList<Episode>()
|
||||
document.select("#seasons > div").reversed().map { element ->
|
||||
val season = element.selectFirst("div.se-q > span.se-t")!!.text().toInt()
|
||||
element.select("div.se-a > ul > li").filter { it -> it.text()!="There are still no episodes this season" }.map{ episode ->
|
||||
val href = episode.selectFirst("div.episodiotitle > a")!!.attr("href")
|
||||
val epNum =episode.selectFirst("div.numerando")!!.text().substringAfter("-").filter { it.isDigit() }.toIntOrNull()
|
||||
val epTitle = episode.selectFirst("div.episodiotitle > a")!!.text()
|
||||
val posterUrl = episode.selectFirst("div.imagen > img")!!.attr("src")
|
||||
episodeList.add(
|
||||
Episode(
|
||||
href,
|
||||
epTitle,
|
||||
season,
|
||||
epNum,
|
||||
posterUrl,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
return TvSeriesLoadResponse(
|
||||
title,
|
||||
url,
|
||||
this.name,
|
||||
type,
|
||||
episodeList,
|
||||
fixUrlNull(poster),
|
||||
year.toIntOrNull(),
|
||||
description,
|
||||
null,
|
||||
rating,
|
||||
null,
|
||||
null,
|
||||
mutableListOf(),
|
||||
recomm
|
||||
)
|
||||
} else {
|
||||
val actors: List<ActorData> =
|
||||
document.select("div.person").filter{ it -> it.selectFirst("div.img > a > img")?.attr("src")!!.contains("/no/cast.png").not()}.map { actordata ->
|
||||
val actorName = actordata.selectFirst("div.data > div.name > a")!!.text()
|
||||
val actorImage : String? = actordata.selectFirst("div.img > a > img")?.attr("src")
|
||||
val roleActor = actordata.selectFirst("div.data > div.caracter")!!.text()
|
||||
ActorData(actor = Actor(actorName, image = actorImage), roleString = roleActor )
|
||||
}
|
||||
return newMovieLoadResponse(
|
||||
title,
|
||||
url,
|
||||
type,
|
||||
url
|
||||
) {
|
||||
posterUrl = fixUrlNull(poster)
|
||||
this.year = year.toIntOrNull()
|
||||
this.plot = description
|
||||
this.rating = rating
|
||||
this.recommendations = recomm
|
||||
this.duration = null
|
||||
this.actors = actors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
val doc = app.get(data).document
|
||||
val type = if( data.contains("film") ){"movie"} else {"tv"}
|
||||
val idpost=doc.select("#player-option-1").attr("data-post")
|
||||
val test = app.post("$mainUrl/wp-admin/admin-ajax.php", headers = mapOf(
|
||||
"content-type" to "application/x-www-form-urlencoded; charset=UTF-8",
|
||||
"accept" to "*/*",
|
||||
"X-Requested-With" to "XMLHttpRequest",
|
||||
), data = mapOf(
|
||||
"action" to "doo_player_ajax",
|
||||
"post" to idpost,
|
||||
"nume" to "1",
|
||||
"type" to type,
|
||||
))
|
||||
|
||||
val url2= Regex("""src='((.|\\n)*?)'""").find(test.text)?.groups?.get(1)?.value.toString()
|
||||
val trueUrl = app.get(url2, headers = mapOf("referer" to mainUrl)).url
|
||||
loadExtractor(trueUrl, data, subtitleCallback, callback)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
|
@ -1,258 +0,0 @@
|
|||
package com.lagradost.cloudstream3.movieproviders
|
||||
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.extractors.Cinestart
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
|
||||
class CinecalidadProvider : MainAPI() {
|
||||
override var mainUrl = "https://cinecalidad.lol"
|
||||
override var name = "Cinecalidad"
|
||||
override var lang = "es"
|
||||
override val hasMainPage = true
|
||||
override val hasChromecastSupport = true
|
||||
override val hasDownloadSupport = true
|
||||
override val supportedTypes = setOf(
|
||||
TvType.Movie,
|
||||
TvType.TvSeries,
|
||||
)
|
||||
override val vpnStatus = VPNStatus.MightBeNeeded //Due to evoload sometimes not loading
|
||||
|
||||
override val mainPage = mainPageOf(
|
||||
Pair("$mainUrl/ver-serie/page/", "Series"),
|
||||
Pair("$mainUrl/page/", "Peliculas"),
|
||||
Pair("$mainUrl/genero-de-la-pelicula/peliculas-en-calidad-4k/page/", "4K UHD"),
|
||||
)
|
||||
|
||||
override suspend fun getMainPage(
|
||||
page: Int,
|
||||
request : MainPageRequest
|
||||
): HomePageResponse {
|
||||
val url = request.data + page
|
||||
|
||||
val soup = app.get(url).document
|
||||
val home = soup.select(".item.movies").map {
|
||||
val title = it.selectFirst("div.in_title")!!.text()
|
||||
val link = it.selectFirst("a")!!.attr("href")
|
||||
TvSeriesSearchResponse(
|
||||
title,
|
||||
link,
|
||||
this.name,
|
||||
if (link.contains("/ver-pelicula/")) TvType.Movie else TvType.TvSeries,
|
||||
it.selectFirst(".poster.custom img")!!.attr("data-src"),
|
||||
null,
|
||||
null,
|
||||
)
|
||||
}
|
||||
|
||||
return newHomePageResponse(request.name, home)
|
||||
}
|
||||
|
||||
override suspend fun search(query: String): List<SearchResponse> {
|
||||
val url = "$mainUrl/?s=${query}"
|
||||
val document = app.get(url).document
|
||||
|
||||
return document.select("article").map {
|
||||
val title = it.selectFirst("div.in_title")!!.text()
|
||||
val href = it.selectFirst("a")!!.attr("href")
|
||||
val image = it.selectFirst(".poster.custom img")!!.attr("data-src")
|
||||
val isMovie = href.contains("/ver-pelicula/")
|
||||
|
||||
if (isMovie) {
|
||||
MovieSearchResponse(
|
||||
title,
|
||||
href,
|
||||
this.name,
|
||||
TvType.Movie,
|
||||
image,
|
||||
null
|
||||
)
|
||||
} else {
|
||||
TvSeriesSearchResponse(
|
||||
title,
|
||||
href,
|
||||
this.name,
|
||||
TvType.TvSeries,
|
||||
image,
|
||||
null,
|
||||
null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override suspend fun load(url: String): LoadResponse? {
|
||||
val soup = app.get(url, timeout = 120).document
|
||||
|
||||
val title = soup.selectFirst(".single_left h1")!!.text()
|
||||
val description = soup.selectFirst("div.single_left table tbody tr td p")?.text()?.trim()
|
||||
val poster: String? = soup.selectFirst(".alignnone")!!.attr("data-src")
|
||||
val episodes = soup.select("div.se-c div.se-a ul.episodios li").map { li ->
|
||||
val href = li.selectFirst("a")!!.attr("href")
|
||||
val epThumb = li.selectFirst("img.lazy")!!.attr("data-src")
|
||||
val name = li.selectFirst(".episodiotitle a")!!.text()
|
||||
val seasonid =
|
||||
li.selectFirst(".numerando")!!.text().replace(Regex("(S|E)"), "").let { str ->
|
||||
str.split("-").mapNotNull { subStr -> subStr.toIntOrNull() }
|
||||
}
|
||||
val isValid = seasonid.size == 2
|
||||
val episode = if (isValid) seasonid.getOrNull(1) else null
|
||||
val season = if (isValid) seasonid.getOrNull(0) else null
|
||||
Episode(
|
||||
href,
|
||||
name,
|
||||
season,
|
||||
episode,
|
||||
if (epThumb.contains("svg")) null else epThumb
|
||||
)
|
||||
}
|
||||
return when (val tvType =
|
||||
if (url.contains("/ver-pelicula/")) TvType.Movie else TvType.TvSeries) {
|
||||
TvType.TvSeries -> {
|
||||
TvSeriesLoadResponse(
|
||||
title,
|
||||
url,
|
||||
this.name,
|
||||
tvType,
|
||||
episodes,
|
||||
poster,
|
||||
null,
|
||||
description,
|
||||
)
|
||||
}
|
||||
TvType.Movie -> {
|
||||
MovieLoadResponse(
|
||||
title,
|
||||
url,
|
||||
this.name,
|
||||
tvType,
|
||||
url,
|
||||
poster,
|
||||
null,
|
||||
description,
|
||||
)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun loadLinks(
|
||||
data: String,
|
||||
isCasting: Boolean,
|
||||
subtitleCallback: (SubtitleFile) -> Unit,
|
||||
callback: (ExtractorLink) -> Unit
|
||||
): Boolean {
|
||||
|
||||
val datam = app.get(data)
|
||||
val doc = datam.document
|
||||
val datatext = datam.text
|
||||
|
||||
doc.select(".dooplay_player_option").apmap {
|
||||
val url = it.attr("data-option")
|
||||
if (url.startsWith("https://cinestart.net")) {
|
||||
val extractor = Cinestart()
|
||||
extractor.getSafeUrl(url, null, subtitleCallback, callback)
|
||||
} else {
|
||||
loadExtractor(url, mainUrl, subtitleCallback, callback)
|
||||
}
|
||||
if (url.startsWith("https://cinecalidad.lol")) {
|
||||
val cineurlregex =
|
||||
Regex("(https:\\/\\/cinecalidad\\.lol\\/play\\/\\?h=[a-zA-Z0-9]{0,8}[a-zA-Z0-9_-]+)")
|
||||
cineurlregex.findAll(url).map {
|
||||
it.value.replace("/play/", "/play/r.php")
|
||||
}.toList().apmap {
|
||||
app.get(
|
||||
it,
|
||||
headers = mapOf(
|
||||
"Host" to "cinecalidad.lol",
|
||||
"User-Agent" to USER_AGENT,
|
||||
"Accept" to "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
||||
"Accept-Language" to "en-US,en;q=0.5",
|
||||
"DNT" to "1",
|
||||
"Connection" to "keep-alive",
|
||||
"Referer" to data,
|
||||
"Upgrade-Insecure-Requests" to "1",
|
||||
"Sec-Fetch-Dest" to "iframe",
|
||||
"Sec-Fetch-Mode" to "navigate",
|
||||
"Sec-Fetch-Site" to "same-origin",
|
||||
"Sec-Fetch-User" to "?1",
|
||||
),
|
||||
allowRedirects = false
|
||||
).okhttpResponse.headers.values("location").apmap { extractedurl ->
|
||||
if (extractedurl.contains("cinestart")) {
|
||||
loadExtractor(extractedurl, mainUrl, subtitleCallback, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (datatext.contains("en castellano")) app.get("$data?ref=es").document.select(".dooplay_player_option")
|
||||
.apmap {
|
||||
val url = it.attr("data-option")
|
||||
if (url.startsWith("https://cinestart.net")) {
|
||||
val extractor = Cinestart()
|
||||
extractor.getSafeUrl(url, null, subtitleCallback, callback)
|
||||
} else {
|
||||
loadExtractor(url, mainUrl, subtitleCallback, callback)
|
||||
}
|
||||
|
||||
if (url.startsWith("https://cinecalidad.lol")) {
|
||||
val cineurlregex =
|
||||
Regex("(https:\\/\\/cinecalidad\\.lol\\/play\\/\\?h=[a-zA-Z0-9]{0,8}[a-zA-Z0-9_-]+)")
|
||||
cineurlregex.findAll(url).map {
|
||||
it.value.replace("/play/", "/play/r.php")
|
||||
}.toList().apmap {
|
||||
app.get(
|
||||
it,
|
||||
headers = mapOf(
|
||||
"Host" to "cinecalidad.lol",
|
||||
"User-Agent" to USER_AGENT,
|
||||
"Accept" to "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
||||
"Accept-Language" to "en-US,en;q=0.5",
|
||||
"DNT" to "1",
|
||||
"Connection" to "keep-alive",
|
||||
"Referer" to data,
|
||||
"Upgrade-Insecure-Requests" to "1",
|
||||
"Sec-Fetch-Dest" to "iframe",
|
||||
"Sec-Fetch-Mode" to "navigate",
|
||||
"Sec-Fetch-Site" to "same-origin",
|
||||
"Sec-Fetch-User" to "?1",
|
||||
),
|
||||
allowRedirects = false
|
||||
).okhttpResponse.headers.values("location").apmap { extractedurl ->
|
||||
if (extractedurl.contains("cinestart")) {
|
||||
loadExtractor(extractedurl, mainUrl, subtitleCallback, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (datatext.contains("Subtítulo LAT") || datatext.contains("Forzados LAT")) {
|
||||
doc.select("#panel_descarga.pane a").apmap {
|
||||
val link =
|
||||
if (data.contains("serie") || data.contains("episodio")) "${data}${it.attr("href")}"
|
||||
else it.attr("href")
|
||||
val docsub = app.get(link)
|
||||
val linksub = docsub.document
|
||||
val validsub = docsub.text
|
||||
if (validsub.contains("Subtítulo") || validsub.contains("Forzados")) {
|
||||
val langregex = Regex("(Subtítulo.*\$|Forzados.*\$)")
|
||||
val langdoc = linksub.selectFirst("div.titulo h3")!!.text()
|
||||
val reallang = langregex.find(langdoc)?.destructured?.component1()
|
||||
linksub.select("a.link").apmap {
|
||||
val sublink =
|
||||
if (data.contains("serie") || data.contains("episodio")) "${data}${
|
||||
it.attr("href")
|
||||
}"
|
||||
else it.attr("href")
|
||||
subtitleCallback(
|
||||
SubtitleFile(reallang!!, sublink)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue