refactor(src/es): Improvements for the main spanish extensions (#2446)

This commit is contained in:
imper1aldev 2023-10-30 06:52:35 -06:00 committed by GitHub
parent d688e06337
commit b48cae3c3c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 2452 additions and 1318 deletions

View File

@ -6,14 +6,17 @@ import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
class VoeExtractor(private val client: OkHttpClient) { class VoeExtractor(private val client: OkHttpClient) {
fun videoFromUrl(url: String, quality: String? = null): Video? { fun videoFromUrl(url: String, quality: String? = null, prefix: String = ""): Video? {
val document = client.newCall(GET(url)).execute().asJsoup() val document = client.newCall(GET(url)).execute().asJsoup()
val script = document.selectFirst("script:containsData(const sources),script:containsData(var sources)") val script = document.selectFirst("script:containsData(const sources),script:containsData(var sources)")
?.data() ?.data()
?: return null ?: return null
val videoUrl = script.substringAfter("hls': '").substringBefore("'") val videoUrl = script.substringAfter("hls': '").substringBefore("'")
val resolution = script.substringAfter("video_height': ").substringBefore(",") val resolution = script.substringAfter("video_height': ").substringBefore(",")
val qualityStr = quality ?: "VoeCDN(${resolution}p)" val qualityStr = when {
prefix.isNotEmpty() -> "$prefix${resolution}p"
else -> quality ?: "VoeCDN(${resolution}p)"
}
return Video(url, qualityStr, videoUrl) return Video(url, qualityStr, videoUrl)
} }
} }

View File

@ -5,7 +5,7 @@ ext {
extName = 'Animefenix' extName = 'Animefenix'
pkgNameSuffix = 'es.animefenix' pkgNameSuffix = 'es.animefenix'
extClass = '.Animefenix' extClass = '.Animefenix'
extVersionCode = 29 extVersionCode = 30
libVersion = '13' libVersion = '13'
} }
@ -13,11 +13,17 @@ dependencies {
implementation(project(':lib-mp4upload-extractor')) implementation(project(':lib-mp4upload-extractor'))
implementation(project(':lib-streamtape-extractor')) implementation(project(':lib-streamtape-extractor'))
implementation(project(':lib-yourupload-extractor')) implementation(project(':lib-yourupload-extractor'))
implementation(project(':lib-uqload-extractor'))
implementation(project(':lib-okru-extractor')) implementation(project(':lib-okru-extractor'))
implementation(project(':lib-burstcloud-extractor')) implementation(project(':lib-burstcloud-extractor'))
implementation(project(':lib-streamwish-extractor')) implementation(project(':lib-streamwish-extractor'))
implementation(project(':lib-filemoon-extractor')) implementation(project(':lib-filemoon-extractor'))
implementation(project(':lib-voe-extractor')) implementation(project(':lib-voe-extractor'))
implementation(project(':lib-streamlare-extractor'))
implementation(project(':lib-fastream-extractor'))
implementation(project(':lib-dood-extractor'))
implementation(project(':lib-upstream-extractor'))
implementation(project(':lib-streamhidevid-extractor'))
} }

View File

@ -7,30 +7,36 @@ import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.animesource.model.AnimeFilter import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import eu.kanade.tachiyomi.animesource.model.AnimesPage
import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
import eu.kanade.tachiyomi.lib.burstcloudextractor.BurstCloudExtractor import eu.kanade.tachiyomi.lib.burstcloudextractor.BurstCloudExtractor
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
import eu.kanade.tachiyomi.lib.fastreamextractor.FastreamExtractor
import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
import eu.kanade.tachiyomi.lib.streamhidevidextractor.StreamHideVidExtractor
import eu.kanade.tachiyomi.lib.streamlareextractor.StreamlareExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
import eu.kanade.tachiyomi.lib.upstreamextractor.UpstreamExtractor
import eu.kanade.tachiyomi.lib.uqloadextractor.UqloadExtractor
import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.net.URLDecoder import java.net.URLDecoder
class Animefenix : ConfigurableAnimeSource, ParsedAnimeHttpSource() { class Animefenix : ConfigurableAnimeSource, AnimeHttpSource() {
override val name = "AnimeFenix" override val name = "AnimeFenix"
@ -42,117 +48,43 @@ class Animefenix : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val client: OkHttpClient = network.cloudflareClient override val client: OkHttpClient = network.cloudflareClient
private val preferences: SharedPreferences by lazy { private val preferences: SharedPreferences by lazy { Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) }
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
override fun popularAnimeSelector(): String = "article.serie-card" companion object {
private const val PREF_QUALITY_KEY = "preferred_quality"
private const val PREF_QUALITY_DEFAULT = "1080"
private val QUALITY_LIST = arrayOf("1080", "720", "480", "360")
private const val PREF_SERVER_KEY = "preferred_server"
private const val PREF_SERVER_DEFAULT = "Amazon"
private val SERVER_LIST = arrayOf(
"YourUpload", "Voe", "Mp4Upload", "Doodstream",
"Upload", "BurstCloud", "Upstream", "StreamTape",
"Fastream", "Filemoon", "StreamWish", "Okru",
"Amazon", "AmazonES", "Fireload", "FileLions"
)
}
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/animes?order=likes&page=$page") override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/animes?order=likes&page=$page")
override fun popularAnimeFromElement(element: Element): SAnime { override fun popularAnimeParse(response: Response): AnimesPage {
val anime = SAnime.create().apply { val document = response.asJsoup()
setUrlWithoutDomain( val elements = document.select("article.serie-card")
element.select("figure.image a").attr("href"), val nextPage = document.select("ul.pagination-list li a.pagination-link:contains(Siguiente)").any()
) val animeList = elements.map { element ->
SAnime.create().apply {
setUrlWithoutDomain(element.select("figure.image a").attr("abs:href"))
title = element.select("div.title h3 a").text() title = element.select("div.title h3 a").text()
thumbnail_url = element.select("figure.image a img").attr("src") thumbnail_url = element.select("figure.image a img").attr("abs:src")
description = element.select("div.serie-card__information p").text() description = element.select("div.serie-card__information p").text()
} }
return anime }
return AnimesPage(animeList, nextPage)
} }
override fun popularAnimeNextPageSelector(): String = "ul.pagination-list li a.pagination-link:contains(Siguiente)" override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/animes?order=added&page=$page")
override fun episodeListParse(response: Response): List<SEpisode> { override fun latestUpdatesParse(response: Response) = popularAnimeParse(response)
val document = response.asJsoup()
return document.select("ul.anime-page__episode-list.is-size-6 li").map { it ->
val epNum = it.select("a span").text().replace("Episodio", "")
SEpisode.create().apply {
episode_number = epNum.toFloat()
name = "Episodio $epNum"
setUrlWithoutDomain(it.select("a").attr("href"))
}
}
}
override fun episodeListSelector() = throw Exception("not used")
override fun episodeFromElement(element: Element) = throw Exception("not used")
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
val videoList = mutableListOf<Video>()
val servers = document.selectFirst("script:containsData(var tabsArray)")!!.data()
.split("tabsArray").map { it.substringAfter("src='").substringBefore("'").replace("amp;", "") }
.filter { it.contains("https") }
servers.forEach { server ->
val decodedUrl = URLDecoder.decode(server, "UTF-8")
val realUrl = try {
client.newCall(GET(decodedUrl)).execute().asJsoup().selectFirst("script")!!
.data().substringAfter("src=\"").substringBefore("\"")
} catch (e: Exception) { "" }
try {
when {
realUrl.contains("ok.ru") || realUrl.contains("okru") -> OkruExtractor(client).videosFromUrl(realUrl).let { videoList.addAll(it) }
realUrl.contains("filemoon") || realUrl.contains("moonplayer") -> FilemoonExtractor(client).videosFromUrl(realUrl, headers = headers).let { videoList.addAll(it) }
realUrl.contains("burstcloud") || realUrl.contains("burst") -> BurstCloudExtractor(client).videoFromUrl(realUrl, headers = headers).let { videoList.addAll(it) }
realUrl.contains("streamwish") || realUrl.contains("embedwish") -> StreamWishExtractor(client, headers).videosFromUrl(realUrl).let { videoList.addAll(it) }
realUrl.contains("streamtape") -> StreamTapeExtractor(client).videoFromUrl(realUrl, "StreamTape")?.let { videoList.add(it) }
realUrl.contains("mp4upload") -> Mp4uploadExtractor(client).videosFromUrl(realUrl, headers).let { videoList.addAll(it) }
realUrl.contains("yourupload") -> YourUploadExtractor(client).videoFromUrl(realUrl, headers = headers).let { videoList.addAll(it) }
realUrl.contains("voe") -> VoeExtractor(client).videoFromUrl(realUrl)?.let { videoList.add(it) }
realUrl.contains("/stream/amz.php?") -> {
val video = amazonExtractor(baseUrl + realUrl.substringAfter(".."))
if (video.isNotBlank()) {
if (realUrl.contains("&ext=es")) {
videoList.add(Video(video, "Amazon ES", video))
} else {
videoList.add(Video(video, "Amazon", video))
}
}
}
realUrl.contains("/stream/fl.php") -> {
val video = realUrl.substringAfter("/stream/fl.php?v=")
if (client.newCall(GET(video)).execute().code == 200) {
videoList.add(Video(video, "FireLoad", video))
}
}
}
} catch (_: Exception) { }
}
return videoList.filter { it.url.contains("https") || it.url.contains("http") }
}
override fun videoListSelector() = throw Exception("not used")
override fun videoUrlParse(document: Document) = throw Exception("not used")
override fun videoFromElement(element: Element) = throw Exception("not used")
override fun List<Video>.sort(): List<Video> {
return try {
val videoSorted = this.sortedWith(
compareBy<Video> { it.quality.replace("[0-9]".toRegex(), "") }.thenByDescending { getNumberFromString(it.quality) },
).toTypedArray()
val userPreferredQuality = preferences.getString("preferred_quality", "Amazon")
val preferredIdx = videoSorted.indexOfFirst { x -> x.quality == userPreferredQuality }
if (preferredIdx != -1) {
videoSorted.drop(preferredIdx + 1)
videoSorted[0] = videoSorted[preferredIdx]
}
videoSorted.toList()
} catch (e: Exception) {
this
}
}
private fun getNumberFromString(epsStr: String): String {
return epsStr.filter { it.isDigit() }.ifEmpty { "0" }
}
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val yearFilter = filters.find { it is YearFilter } as YearFilter val yearFilter = filters.find { it is YearFilter } as YearFilter
@ -190,19 +122,137 @@ class Animefenix : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} }
} }
override fun searchAnimeFromElement(element: Element): SAnime = popularAnimeFromElement(element) override fun searchAnimeParse(response: Response) = popularAnimeParse(response)
override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector() override fun episodeListParse(response: Response): List<SEpisode> {
val document = response.asJsoup()
return document.select("ul.anime-page__episode-list.is-size-6 li").map { it ->
val epNum = it.select("a span").text().replace("Episodio", "")
SEpisode.create().apply {
episode_number = epNum.toFloat()
name = "Episodio $epNum"
setUrlWithoutDomain(it.select("a").attr("abs:href"))
}
}
}
override fun searchAnimeSelector(): String = popularAnimeSelector() override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
val videoList = mutableListOf<Video>()
val servers = document.selectFirst("script:containsData(var tabsArray)")!!.data()
.split("tabsArray").map { it.substringAfter("src='").substringBefore("'").replace("amp;", "") }
.filter { it.contains("https") }
override fun animeDetailsParse(document: Document): SAnime { servers.forEach { server ->
val anime = SAnime.create().apply { val decodedUrl = URLDecoder.decode(server, "UTF-8")
val realUrl = try {
client.newCall(GET(decodedUrl)).execute().asJsoup().selectFirst("script")!!
.data().substringAfter("src=\"").substringBefore("\"")
} catch (e: Exception) { "" }
try {
serverVideoResolver(realUrl).let { videoList.addAll(it) }
} catch (_: Exception) { }
}
return videoList.filter { it.url.contains("https") || it.url.contains("http") }
}
private fun serverVideoResolver(url: String): List<Video> {
val videoList = mutableListOf<Video>()
val embedUrl = url.lowercase()
try {
if (embedUrl.contains("voe")) {
VoeExtractor(client).videoFromUrl(url, prefix = "Voe:")?.let { videoList.add(it) }
}
if ((embedUrl.contains("amazon") || embedUrl.contains("amz")) && !embedUrl.contains("disable")) {
val video = amazonExtractor(baseUrl + url.substringAfter(".."))
if (video.isNotBlank()) {
if (url.contains("&ext=es")) {
videoList.add(Video(video, "AmazonES", video))
} else {
videoList.add(Video(video, "Amazon", video))
}
}
}
if (embedUrl.contains("ok.ru") || embedUrl.contains("okru")) {
OkruExtractor(client).videosFromUrl(url).also(videoList::addAll)
}
if (embedUrl.contains("filemoon") || embedUrl.contains("moonplayer")) {
val vidHeaders = headers.newBuilder()
.add("Origin", "https://${url.toHttpUrl().host}")
.add("Referer", "https://${url.toHttpUrl().host}/")
.build()
FilemoonExtractor(client).videosFromUrl(url, prefix = "Filemoon:", headers = vidHeaders).also(videoList::addAll)
}
if (embedUrl.contains("uqload")) {
UqloadExtractor(client).videosFromUrl(url).also(videoList::addAll)
}
if (embedUrl.contains("mp4upload")) {
Mp4uploadExtractor(client).videosFromUrl(url, headers).let { videoList.addAll(it) }
}
if (embedUrl.contains("wishembed") || embedUrl.contains("embedwish") || embedUrl.contains("streamwish") || embedUrl.contains("strwish") || embedUrl.contains("wish")) {
val docHeaders = headers.newBuilder()
.add("Origin", "https://streamwish.to")
.add("Referer", "https://streamwish.to/")
.build()
StreamWishExtractor(client, docHeaders).videosFromUrl(url, videoNameGen = { "StreamWish:$it" }).also(videoList::addAll)
}
if (embedUrl.contains("doodstream") || embedUrl.contains("dood.")) {
DoodExtractor(client).videoFromUrl(url, "DoodStream", false)?.let { videoList.add(it) }
}
if (embedUrl.contains("streamlare")) {
StreamlareExtractor(client).videosFromUrl(url).let { videoList.addAll(it) }
}
if (embedUrl.contains("yourupload") || embedUrl.contains("upload")) {
YourUploadExtractor(client).videoFromUrl(url, headers = headers).let { videoList.addAll(it) }
}
if (embedUrl.contains("burstcloud") || embedUrl.contains("burst")) {
BurstCloudExtractor(client).videoFromUrl(url, headers = headers).let { videoList.addAll(it) }
}
if (embedUrl.contains("fastream")) {
FastreamExtractor(client).videoFromUrl(url).let { videoList.addAll(it) }
}
if (embedUrl.contains("upstream")) {
UpstreamExtractor(client).videosFromUrl(url).let { videoList.addAll(it) }
}
if (embedUrl.contains("streamtape") || embedUrl.contains("stp") || embedUrl.contains("stape")) {
StreamTapeExtractor(client).videoFromUrl(url)?.let { videoList.add(it) }
}
if (embedUrl.contains("ahvsh") || embedUrl.contains("streamhide")) {
StreamHideVidExtractor(client).videosFromUrl(url).let { videoList.addAll(it) }
}
if (embedUrl.contains("/stream/fl.php")) {
val video = url.substringAfter("/stream/fl.php?v=")
if (client.newCall(GET(video)).execute().code == 200) {
videoList.add(Video(video, "FireLoad", video))
}
}
if (embedUrl.contains("filelions") || embedUrl.contains("lion")) {
StreamWishExtractor(client, headers).videosFromUrl(url, videoNameGen = { "FileLions:$it" }).also(videoList::addAll)
}
} catch (_: Exception) { }
return videoList
}
override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
return this.sortedWith(
compareBy(
{ it.quality.contains(server, true) },
{ it.quality.contains(quality) },
{ Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 },
),
).reversed()
}
override fun animeDetailsParse(response: Response): SAnime {
val document = response.asJsoup()
return SAnime.create().apply {
title = document.select("h1.title.has-text-orange").text() title = document.select("h1.title.has-text-orange").text()
genre = document.select("a.button.is-small.is-orange.is-outlined.is-roundedX").joinToString { it.text() } genre = document.select("a.button.is-small.is-orange.is-outlined.is-roundedX").joinToString { it.text() }
status = parseStatus(document.select("div.column.is-12-mobile.xis-3-tablet.xis-3-desktop.xhas-background-danger.is-narrow-tablet.is-narrow-desktop a").text()) status = parseStatus(document.select("div.column.is-12-mobile.xis-3-tablet.xis-3-desktop.xhas-background-danger.is-narrow-tablet.is-narrow-desktop a").text())
} }
return anime
} }
private fun parseStatus(statusString: String): Int { private fun parseStatus(statusString: String): Int {
@ -213,14 +263,6 @@ class Animefenix : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} }
} }
override fun latestUpdatesNextPageSelector() = popularAnimeNextPageSelector()
override fun latestUpdatesFromElement(element: Element) = popularAnimeFromElement(element)
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/animes?order=added&page=$page")
override fun latestUpdatesSelector() = popularAnimeSelector()
private fun amazonExtractor(url: String): String { private fun amazonExtractor(url: String): String {
val document = client.newCall(GET(url)).execute().asJsoup() val document = client.newCall(GET(url)).execute().asJsoup()
val videoURl = document.selectFirst("script:containsData(sources: [)")!!.data() val videoURl = document.selectFirst("script:containsData(sources: [)")!!.data()
@ -340,16 +382,12 @@ class Animefenix : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} }
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
val qualities = arrayOf( ListPreference(screen.context).apply {
"Okru:1080p", "Okru:720p", "Okru:480p", "Okru:360p", "Okru:240p", "Okru:144p", // Okru key = PREF_QUALITY_KEY
"Amazon", "AmazonES", "StreamTape", "Fireload", "Mp4upload", "YourUpload", "StreamWish",
)
val videoQualityPref = ListPreference(screen.context).apply {
key = "preferred_quality"
title = "Preferred quality" title = "Preferred quality"
entries = qualities entries = QUALITY_LIST
entryValues = qualities entryValues = QUALITY_LIST
setDefaultValue("Amazon") setDefaultValue(PREF_QUALITY_DEFAULT)
summary = "%s" summary = "%s"
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
@ -358,8 +396,22 @@ class Animefenix : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val entry = entryValues[index] as String val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit() preferences.edit().putString(key, entry).commit()
} }
} }.also(screen::addPreference)
screen.addPreference(videoQualityPref) ListPreference(screen.context).apply {
key = PREF_SERVER_KEY
title = "Preferred server"
entries = SERVER_LIST
entryValues = SERVER_LIST
setDefaultValue(PREF_SERVER_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}.also(screen::addPreference)
} }
} }

View File

@ -1,11 +1,12 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext { ext {
extName = 'AnimeFLV' extName = 'AnimeFLV'
pkgNameSuffix = 'es.animeflv' pkgNameSuffix = 'es.animeflv'
extClass = '.AnimeFlv' extClass = '.AnimeFlv'
extVersionCode = 50 extVersionCode = 51
libVersion = '13' libVersion = '13'
} }

View File

@ -41,7 +41,7 @@ class AnimeFlv : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val lang = "es" override val lang = "es"
override val supportsLatest = false override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient override val client: OkHttpClient = network.cloudflareClient
@ -51,24 +51,30 @@ class AnimeFlv : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
} }
companion object {
private const val PREF_QUALITY_KEY = "preferred_quality"
private const val PREF_QUALITY_DEFAULT = "1080"
private val QUALITY_LIST = arrayOf("1080", "720", "480", "360")
private const val PREF_SERVER_KEY = "preferred_server"
private const val PREF_SERVER_DEFAULT = "YourUpload"
private val SERVER_LIST = arrayOf("MailRu", "Okru", "YourUpload", "DoodStream", "StreamTape")
}
override fun popularAnimeSelector(): String = "div.Container ul.ListAnimes li article" override fun popularAnimeSelector(): String = "div.Container ul.ListAnimes li article"
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/browse?order=rating&page=$page") override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/browse?order=rating&page=$page")
override fun popularAnimeFromElement(element: Element): SAnime { override fun popularAnimeFromElement(element: Element): SAnime {
val anime = SAnime.create() val anime = SAnime.create()
anime.setUrlWithoutDomain( anime.setUrlWithoutDomain(element.select("div.Description a.Button").attr("abs:href"))
baseUrl + element.select("div.Description a.Button")
.attr("href"),
)
anime.title = element.select("a h3").text() anime.title = element.select("a h3").text()
anime.thumbnail_url = try { anime.thumbnail_url = try {
element.select("a div.Image figure img").attr("src") element.select("a div.Image figure img").attr("src")
} catch (e: Exception) { } catch (e: Exception) {
element.select("a div.Image figure img").attr("data-cfsrc") element.select("a div.Image figure img").attr("data-cfsrc")
} }
anime.description = anime.description = element.select("div.Description p:eq(2)").text().removeSurrounding("\"")
element.select("div.Description p:eq(2)").text().removeSurrounding("\"")
return anime return anime
} }
@ -127,7 +133,7 @@ class AnimeFlv : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val docHeaders = headers.newBuilder() val docHeaders = headers.newBuilder()
.add("Referer", "$baseUrl/") .add("Referer", "$baseUrl/")
.build() .build()
StreamWishExtractor(client, docHeaders).videosFromUrl(url, "StreamWish") StreamWishExtractor(client, docHeaders).videosFromUrl(url, videoNameGen = { "StreamWish:$it" })
} }
else -> null else -> null
} }
@ -145,27 +151,6 @@ class AnimeFlv : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun videoFromElement(element: Element) = throw Exception("not used") override fun videoFromElement(element: Element) = throw Exception("not used")
override fun List<Video>.sort(): List<Video> {
return try {
val videoSorted = this.sortedWith(
compareBy<Video> { it.quality.replace("[0-9]".toRegex(), "") }.thenByDescending { getNumberFromString(it.quality) },
).toTypedArray()
val userPreferredQuality = preferences.getString("preferred_quality", "Okru:720p")
val preferredIdx = videoSorted.indexOfFirst { x -> x.quality == userPreferredQuality }
if (preferredIdx != -1) {
videoSorted.drop(preferredIdx + 1)
videoSorted[0] = videoSorted[preferredIdx]
}
videoSorted.toList()
} catch (e: Exception) {
this
}
}
private fun getNumberFromString(epsStr: String): String {
return epsStr.filter { it.isDigit() }.ifEmpty { "0" }
}
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val filterList = if (filters.isEmpty()) getFilterList() else filters val filterList = if (filters.isEmpty()) getFilterList() else filters
val genreFilter = filterList.find { it is GenreFilter } as GenreFilter val genreFilter = filterList.find { it is GenreFilter } as GenreFilter
@ -278,9 +263,7 @@ class AnimeFlv : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
fun toUriPart() = vals[state].second fun toUriPart() = vals[state].second
} }
override fun searchAnimeFromElement(element: Element): SAnime { override fun searchAnimeFromElement(element: Element) = popularAnimeFromElement(element)
return popularAnimeFromElement(element)
}
override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector() override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector()
@ -288,7 +271,7 @@ class AnimeFlv : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun animeDetailsParse(document: Document): SAnime { override fun animeDetailsParse(document: Document): SAnime {
val anime = SAnime.create() val anime = SAnime.create()
anime.thumbnail_url = externalOrInternalImg(document.selectFirst("div.AnimeCover div.Image figure img")!!.attr("src")) anime.thumbnail_url = document.selectFirst("div.AnimeCover div.Image figure img")!!.attr("abs:src")
anime.title = document.selectFirst("div.Ficha.fchlt div.Container .Title")!!.text() anime.title = document.selectFirst("div.Ficha.fchlt div.Container .Title")!!.text()
anime.description = document.selectFirst("div.Description")!!.text().removeSurrounding("\"") anime.description = document.selectFirst("div.Description")!!.text().removeSurrounding("\"")
anime.genre = document.select("nav.Nvgnrs a").joinToString { it.text() } anime.genre = document.select("nav.Nvgnrs a").joinToString { it.text() }
@ -296,10 +279,6 @@ class AnimeFlv : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
return anime return anime
} }
private fun externalOrInternalImg(url: String): String {
return if (url.contains("https")) url else "$baseUrl/$url"
}
private fun parseStatus(statusString: String): Int { private fun parseStatus(statusString: String): Int {
return when { return when {
statusString.contains("En emision") -> SAnime.ONGOING statusString.contains("En emision") -> SAnime.ONGOING
@ -316,19 +295,25 @@ class AnimeFlv : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun latestUpdatesSelector() = popularAnimeSelector() override fun latestUpdatesSelector() = popularAnimeSelector()
override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
return this.sortedWith(
compareBy(
{ it.quality.contains(server, true) },
{ it.quality.contains(quality) },
{ Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 },
),
).reversed()
}
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
val videoQualityPref = ListPreference(screen.context).apply { ListPreference(screen.context).apply {
key = "preferred_quality" key = PREF_SERVER_KEY
title = "Preferred quality" title = "Preferred server"
entries = arrayOf( entries = SERVER_LIST
"Okru:1080p", "Okru:720p", "Okru:480p", "Okru:360p", "Okru:240p", "Okru:144p", // Okru entryValues = SERVER_LIST
"YourUpload", "DoodStream", "StreamTape", setDefaultValue(PREF_SERVER_DEFAULT)
) // video servers without resolution
entryValues = arrayOf(
"Okru:1080p", "Okru:720p", "Okru:480p", "Okru:360p", "Okru:240p", "Okru:144p", // Okru
"YourUpload", "DoodStream", "StreamTape",
) // video servers without resolution
setDefaultValue("Okru:720p")
summary = "%s" summary = "%s"
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
@ -337,7 +322,22 @@ class AnimeFlv : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val entry = entryValues[index] as String val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit() preferences.edit().putString(key, entry).commit()
} }
}.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY
title = "Preferred quality"
entries = QUALITY_LIST
entryValues = QUALITY_LIST
setDefaultValue(PREF_QUALITY_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
} }
screen.addPreference(videoQualityPref) }.also(screen::addPreference)
} }
} }

View File

@ -5,7 +5,7 @@ ext {
extName = 'AnimeID' extName = 'AnimeID'
pkgNameSuffix = 'es.animeid' pkgNameSuffix = 'es.animeid'
extClass = '.AnimeID' extClass = '.AnimeID'
extVersionCode = 6 extVersionCode = 7
libVersion = '13' libVersion = '13'
} }

View File

@ -28,7 +28,6 @@ import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.net.URI
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
@ -52,7 +51,7 @@ class AnimeID : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun popularAnimeSelector(): String = "#result article.item" override fun popularAnimeSelector(): String = "#result article.item"
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/series?sort=newest&pag=$page") override fun popularAnimeRequest(page: Int) = GET("$baseUrl/series?sort=views&pag=$page")
override fun popularAnimeFromElement(element: Element): SAnime { override fun popularAnimeFromElement(element: Element): SAnime {
val anime = SAnime.create() val anime = SAnime.create()
@ -65,12 +64,11 @@ class AnimeID : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun popularAnimeNextPageSelector(): String = "#paginas ul li:nth-last-child(2) a" override fun popularAnimeNextPageSelector(): String = "#paginas ul li:nth-last-child(2) a"
// override fun episodeListSelector() = "ul.ListCaps li a"
override fun episodeListSelector() = throw Exception("not used") override fun episodeListSelector() = throw Exception("not used")
override fun episodeListParse(response: Response): List<SEpisode> { override fun episodeListParse(response: Response): List<SEpisode> {
val document = response.asJsoup() val document = response.asJsoup()
var animeId = document.select("#ord").attr("data-id") val animeId = document.select("#ord").attr("data-id")
return episodeJsonParse(response.request.url.toString(), animeId) return episodeJsonParse(response.request.url.toString(), animeId)
} }
@ -86,15 +84,15 @@ class AnimeID : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
.set("Accept-Language", "es-MX,es-419;q=0.9,es;q=0.8,en;q=0.7") .set("Accept-Language", "es-MX,es-419;q=0.9,es;q=0.8,en;q=0.7")
.build() .build()
var responseString = client.newCall(GET("https://www.animeid.tv/ajax/caps?id=$animeId&ord=DESC&pag=$nextPage", headers)) val responseString = client.newCall(GET("https://www.animeid.tv/ajax/caps?id=$animeId&ord=DESC&pag=$nextPage", headers))
.execute().asJsoup().body()!!.toString().substringAfter("<body>").substringBefore("</body>") .execute().asJsoup().body()!!.toString().substringAfter("<body>").substringBefore("</body>")
val jObject = json.decodeFromString<JsonObject>(responseString) val jObject = json.decodeFromString<JsonObject>(responseString)
var listCaps = jObject["list"]!!.jsonArray val listCaps = jObject["list"]!!.jsonArray
listCaps!!.forEach { cap -> listCaps!!.forEach { cap ->
var capParsed = cap.jsonObject val capParsed = cap.jsonObject
val epNum = capParsed["numero"]!!.jsonPrimitive.content!!.toFloat() val epNum = capParsed["numero"]!!.jsonPrimitive.content!!.toFloat()
val episode = SEpisode.create() val episode = SEpisode.create()
var dateUpload = manualDateParse(capParsed["date"]!!.jsonPrimitive.content!!.toString()) val dateUpload = manualDateParse(capParsed["date"]!!.jsonPrimitive.content!!.toString())
episode.episode_number = epNum episode.episode_number = epNum
episode.name = "Episodio $epNum" episode.name = "Episodio $epNum"
dateUpload!!.also { episode.date_upload = it } dateUpload!!.also { episode.date_upload = it }
@ -126,28 +124,17 @@ class AnimeID : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val document = response.asJsoup() val document = response.asJsoup()
val videoList = mutableListOf<Video>() val videoList = mutableListOf<Video>()
document.select("#partes div.container li.subtab div.parte").forEach { script -> document.select("#partes div.container li.subtab div.parte").forEach { script ->
var jsonString = script.attr("data") val jsonString = script.attr("data")
val jsonUnescape = unescapeJava(jsonString)!!.replace("\\", "") val jsonUnescape = unescapeJava(jsonString)!!.replace("\\", "")
val url = jsonUnescape!!.substringAfter("src=\"").substringBefore("\"").replace("\\\\", "\\") val url = jsonUnescape.substringAfter("src=\"").substringBefore("\"").replace("\\\\", "\\")
val quality = getDomainName(url) if (url.contains("streamtape")) {
if (quality == "streamtape") { StreamTapeExtractor(client).videoFromUrl(url)?.let { videoList.add(it) }
val video = StreamTapeExtractor(client).videoFromUrl(url, "Streamtape")
if (video != null) {
videoList.add(video)
}
} }
} }
return videoList return videoList
} }
private fun getDomainName(url: String?): String? { private fun unescapeJava(escaped: String): String {
val uri = URI(url)
val domain: String = uri.host
val preDomain = if (domain.startsWith("www.")) domain.substring(4)!!.split(".") else domain!!.split(".")
return if (preDomain.count() == 3) preDomain[1] else preDomain[0]
}
private fun unescapeJava(escaped: String): String? {
var escaped = escaped var escaped = escaped
if (escaped.indexOf("\\u") == -1) return escaped if (escaped.indexOf("\\u") == -1) return escaped
var processed = "" var processed = ""
@ -165,9 +152,7 @@ class AnimeID : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun animeDetailsParse(document: Document): SAnime { override fun animeDetailsParse(document: Document): SAnime {
val anime = SAnime.create() val anime = SAnime.create()
anime.thumbnail_url = externalOrInternalImg( anime.thumbnail_url = document.selectFirst("#anime figure img.cover")!!.attr("abs:src")
document.selectFirst("#anime figure img.cover")!!.attr("src"),
)
anime.title = document.selectFirst("#anime section hgroup h1")!!.text() anime.title = document.selectFirst("#anime section hgroup h1")!!.text()
anime.description = document.selectFirst("#anime section p.sinopsis")!!.text().removeSurrounding("\"") anime.description = document.selectFirst("#anime section p.sinopsis")!!.text().removeSurrounding("\"")
anime.genre = document.select("#anime section ul.tags li a").joinToString { it.text() } anime.genre = document.select("#anime section ul.tags li a").joinToString { it.text() }
@ -181,37 +166,17 @@ class AnimeID : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun videoFromElement(element: Element) = throw Exception("not used") override fun videoFromElement(element: Element) = throw Exception("not used")
override fun List<Video>.sort(): List<Video> {
return try {
val videoSorted = this.sortedWith(
compareBy<Video> { it.quality.replace("[0-9]".toRegex(), "") }.thenByDescending { getNumberFromString(it.quality) },
).toTypedArray()
val userPreferredQuality = preferences.getString("preferred_quality", "StreamTape")
val preferredIdx = videoSorted.indexOfFirst { x -> x.quality == userPreferredQuality }
if (preferredIdx != -1) {
videoSorted.drop(preferredIdx + 1)
videoSorted[0] = videoSorted[preferredIdx]
}
videoSorted.toList()
} catch (e: Exception) {
this
}
}
private fun getNumberFromString(epsStr: String): String {
return epsStr.filter { it.isDigit() }.ifEmpty { "0" }
}
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val filterList = if (filters.isEmpty()) getFilterList() else filters val filterList = if (filters.isEmpty()) getFilterList() else filters
val genreFilter = filterList.find { it is GenreFilter } as GenreFilter val genreFilter = filterList.find { it is GenreFilter } as GenreFilter
val orderByFilter = filters.find { it is OrderByFilter } as OrderByFilter
var request = when { return when {
query.isNotBlank() -> GET("$baseUrl/buscar?q=$query&pag=$page&sort=newest") query.isNotBlank() -> GET("$baseUrl/buscar?q=$query&pag=$page&sort=${orderByFilter.toUriPart()}")
genreFilter.state != 0 -> GET("$baseUrl/genero/${genreFilter.toUriPart()}?pag=$page&sort=newest") genreFilter.state != 0 -> GET("$baseUrl/genero/${genreFilter.toUriPart()}?pag=$page&sort=${orderByFilter.toUriPart()}")
else -> GET("$baseUrl/series?sort=newest&pag=$page") orderByFilter.state != 0 -> GET("$baseUrl/series?sort=${orderByFilter.toUriPart()}&pag=$page")
else -> popularAnimeRequest(page)
} }
return request
} }
override fun searchAnimeFromElement(element: Element): SAnime = popularAnimeFromElement(element) override fun searchAnimeFromElement(element: Element): SAnime = popularAnimeFromElement(element)
@ -223,10 +188,11 @@ class AnimeID : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun getFilterList(): AnimeFilterList = AnimeFilterList( override fun getFilterList(): AnimeFilterList = AnimeFilterList(
AnimeFilter.Header("La busqueda por texto ignora el filtro"), AnimeFilter.Header("La busqueda por texto ignora el filtro"),
GenreFilter(), GenreFilter(),
OrderByFilter(),
) )
private class GenreFilter : UriPartFilter( private class GenreFilter : UriPartFilter(
"Generos", "Géneros",
arrayOf( arrayOf(
Pair("<Selecionar>", ""), Pair("<Selecionar>", ""),
Pair("+18", "18"), Pair("+18", "18"),
@ -356,9 +322,15 @@ class AnimeID : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
fun toUriPart() = vals[state].second fun toUriPart() = vals[state].second
} }
private fun externalOrInternalImg(url: String): String { private class OrderByFilter : UriPartFilter(
return if (url.contains("https")) url else "$baseUrl/$url" "Ordenar Por",
} arrayOf(
Pair("<Seleccionar>", ""),
Pair("Recientes", "newest"),
Pair("A-Z", "asc"),
Pair("Más vistos", "views"),
),
)
private fun parseStatus(statusString: String): Int { private fun parseStatus(statusString: String): Int {
return when { return when {
@ -372,17 +344,17 @@ class AnimeID : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun latestUpdatesFromElement(element: Element) = popularAnimeFromElement(element) override fun latestUpdatesFromElement(element: Element) = popularAnimeFromElement(element)
override fun latestUpdatesRequest(page: Int) = popularAnimeRequest(page) override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/series?sort=newest&pag=$page")
override fun latestUpdatesSelector() = popularAnimeSelector() override fun latestUpdatesSelector() = popularAnimeSelector()
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
val videoQualityPref = ListPreference(screen.context).apply { ListPreference(screen.context).apply {
key = "preferred_quality" key = "preferred_quality"
title = "Preferred quality" title = "Preferred server"
entries = arrayOf("Stape", "Streamtape", "hd", "sd", "low", "lowest", "mobile") entries = arrayOf("StreamTape")
entryValues = arrayOf("Stape", "Streamtape", "hd", "sd", "low", "lowest", "mobile") entryValues = arrayOf("StreamTape")
setDefaultValue("Streamtape") setDefaultValue("StreamTape")
summary = "%s" summary = "%s"
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
@ -391,7 +363,6 @@ class AnimeID : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val entry = entryValues[index] as String val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit() preferences.edit().putString(key, entry).commit()
} }
} }.also(screen::addPreference)
screen.addPreference(videoQualityPref)
} }
} }

View File

@ -5,7 +5,7 @@ ext {
extName = 'AnimeLatinoHD' extName = 'AnimeLatinoHD'
pkgNameSuffix = 'es.animelatinohd' pkgNameSuffix = 'es.animelatinohd'
extClass = '.AnimeLatinoHD' extClass = '.AnimeLatinoHD'
extVersionCode = 28 extVersionCode = 29
libVersion = '13' libVersion = '13'
} }
@ -14,6 +14,7 @@ dependencies {
implementation(project(':lib-streamtape-extractor')) implementation(project(':lib-streamtape-extractor'))
implementation(project(':lib-okru-extractor')) implementation(project(':lib-okru-extractor'))
implementation(project(':lib-dood-extractor')) implementation(project(':lib-dood-extractor'))
implementation(project(':lib-streamwish-extractor'))
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View File

@ -12,14 +12,14 @@ import eu.kanade.tachiyomi.animesource.model.AnimesPage
import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
@ -27,16 +27,15 @@ import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.jsonPrimitive
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
class AnimeLatinoHD : ConfigurableAnimeSource, ParsedAnimeHttpSource() { class AnimeLatinoHD : ConfigurableAnimeSource, AnimeHttpSource() {
override val name = "AnimeLatinoHD" override val name = "AnimeLatinoHD"
@ -54,13 +53,31 @@ class AnimeLatinoHD : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
} }
override fun popularAnimeSelector(): String = "#__next main[class*='Animes_container'] div[class*='ListAnimes_box'] div[class*='ListAnimes'] div[class*='AnimeCard_anime']" companion object {
const val PREF_QUALITY_KEY = "preferred_quality"
const val PREF_QUALITY_DEFAULT = "1080"
private val QUALITY_LIST = arrayOf("1080", "720", "480", "360")
private const val PREF_SERVER_KEY = "preferred_server"
private const val PREF_SERVER_DEFAULT = "FileLions"
private val SERVER_LIST = arrayOf(
"YourUpload", "BurstCloud", "Voe", "Mp4Upload", "Doodstream",
"Upload", "BurstCloud", "Upstream", "StreamTape", "Amazon",
"Fastream", "Filemoon", "StreamWish", "Okru", "Streamlare",
"FileLions", "StreamHideVid", "SolidFiles", "Od.lk", "CldUp",
)
private const val PREF_LANGUAGE_KEY = "preferred_language"
private const val PREF_LANGUAGE_DEFAULT = "[LAT]"
private val LANGUAGE_LIST = arrayOf("[LAT]", "[SUB]")
}
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/animes/populares") override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/animes/populares")
override fun popularAnimeParse(response: Response): AnimesPage { override fun popularAnimeParse(response: Response): AnimesPage {
val document = response.asJsoup() val document = response.asJsoup()
val animeList = mutableListOf<SAnime>() val animeList = mutableListOf<SAnime>()
val url = response.request.url.toString().lowercase()
val hasNextPage = document.select("#__next > main > div > div[class*=\"Animes_paginate\"] a:last-child svg").any() val hasNextPage = document.select("#__next > main > div > div[class*=\"Animes_paginate\"] a:last-child svg").any()
document.select("script").forEach { script -> document.select("script").forEach { script ->
if (script.data().contains("{\"props\":{\"pageProps\":")) { if (script.data().contains("{\"props\":{\"pageProps\":")) {
@ -68,6 +85,17 @@ class AnimeLatinoHD : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val props = jObject["props"]!!.jsonObject val props = jObject["props"]!!.jsonObject
val pageProps = props["pageProps"]!!.jsonObject val pageProps = props["pageProps"]!!.jsonObject
val data = pageProps["data"]!!.jsonObject val data = pageProps["data"]!!.jsonObject
if (url.contains("status=1")) {
val latestData = data["data"]!!.jsonArray
latestData.forEach { item ->
val animeItem = item!!.jsonObject
val anime = SAnime.create()
anime.setUrlWithoutDomain(externalOrInternalImg("anime/${animeItem["slug"]!!.jsonPrimitive!!.content}"))
anime.thumbnail_url = "https://image.tmdb.org/t/p/w200${animeItem["poster"]!!.jsonPrimitive!!.content}"
anime.title = animeItem["name"]!!.jsonPrimitive!!.content
animeList.add(anime)
}
} else {
val popularToday = data["popular_today"]!!.jsonArray val popularToday = data["popular_today"]!!.jsonArray
popularToday.forEach { item -> popularToday.forEach { item ->
val animeItem = item!!.jsonObject val animeItem = item!!.jsonObject
@ -79,12 +107,13 @@ class AnimeLatinoHD : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} }
} }
} }
}
return AnimesPage(animeList, hasNextPage) return AnimesPage(animeList, hasNextPage)
} }
override fun popularAnimeFromElement(element: Element) = throw Exception("not used") override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/animes?page=$page&status=1")
override fun popularAnimeNextPageSelector(): String = "uwu" override fun latestUpdatesParse(response: Response) = popularAnimeParse(response)
override fun animeDetailsParse(response: Response): SAnime { override fun animeDetailsParse(response: Response): SAnime {
val document = response.asJsoup() val document = response.asJsoup()
@ -107,8 +136,6 @@ class AnimeLatinoHD : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
return newAnime return newAnime
} }
override fun animeDetailsParse(document: Document) = throw Exception("not used")
override fun episodeListParse(response: Response): List<SEpisode> { override fun episodeListParse(response: Response): List<SEpisode> {
val document = response.asJsoup() val document = response.asJsoup()
val episodeList = mutableListOf<SEpisode>() val episodeList = mutableListOf<SEpisode>()
@ -132,12 +159,8 @@ class AnimeLatinoHD : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
return episodeList return episodeList
} }
override fun episodeListSelector() = "uwu"
override fun episodeFromElement(element: Element) = throw Exception("not used")
private fun parseJsonArray(json: JsonElement?): List<JsonElement> { private fun parseJsonArray(json: JsonElement?): List<JsonElement> {
var list = mutableListOf<JsonElement>() val list = mutableListOf<JsonElement>()
json!!.jsonObject!!.entries!!.forEach { list.add(it.value) } json!!.jsonObject!!.entries!!.forEach { list.add(it.value) }
return list return list
} }
@ -175,35 +198,29 @@ class AnimeLatinoHD : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
).execute() ).execute()
val locationsDdh = request!!.networkResponse.toString() val locationsDdh = request!!.networkResponse.toString()
fetchUrls(locationsDdh).map { url -> fetchUrls(locationsDdh).map { url ->
val language = if (item["languaje"]!!.jsonPrimitive!!.content == "1") "[Lat] " else "[Sub] " val language = if (item["languaje"]!!.jsonPrimitive!!.content == "1") "[LAT]" else "[SUB]"
val embedUrl = url.lowercase() val embedUrl = url.lowercase()
if (embedUrl.contains("filemoon")) { if (embedUrl.contains("filemoon")) {
FilemoonExtractor(client).videosFromUrl(url, language) val vidHeaders = headers.newBuilder()
.also(videoList::addAll) .add("Origin", "https://${url.toHttpUrl().host}")
.add("Referer", "https://${url.toHttpUrl().host}/")
.build()
FilemoonExtractor(client).videosFromUrl(url, prefix = "$language Filemoon:", headers = vidHeaders).also(videoList::addAll)
}
if (embedUrl.contains("filelions") || embedUrl.contains("lion")) {
StreamWishExtractor(client, headers).videosFromUrl(url, videoNameGen = { "$language FileLions:$it" }).also(videoList::addAll)
} }
if (embedUrl.contains("streamtape")) { if (embedUrl.contains("streamtape")) {
val video = StreamTapeExtractor(client).videoFromUrl(url, language + "Streamtape") StreamTapeExtractor(client).videoFromUrl(url, "$language Streamtape")?.let { videoList.add(it) }
if (video != null) {
videoList.add(video)
}
} }
if (embedUrl.contains("dood")) { if (embedUrl.contains("dood")) {
val video = try { DoodExtractor(client).videoFromUrl(url, "$language DoodStream")?.let { videoList.add(it) }
DoodExtractor(client).videoFromUrl(url, language + "DoodStream")
} catch (e: Exception) {
null
}
if (video != null) {
videoList.add(video)
}
} }
if (embedUrl.contains("okru") || embedUrl.contains("ok.ru")) { if (embedUrl.contains("okru") || embedUrl.contains("ok.ru")) {
val videos = OkruExtractor(client).videosFromUrl(url, language) OkruExtractor(client).videosFromUrl(url, language).also(videoList::addAll)
videoList.addAll(videos)
} }
if (embedUrl.contains("solidfiles")) { if (embedUrl.contains("solidfiles")) {
val videos = SolidFilesExtractor(client).videosFromUrl(url, language) SolidFilesExtractor(client).videosFromUrl(url, language).also(videoList::addAll)
videoList.addAll(videos)
} }
if (embedUrl.contains("od.lk")) { if (embedUrl.contains("od.lk")) {
videoList.add(Video(url, language + "Od.lk", url)) videoList.add(Video(url, language + "Od.lk", url))
@ -219,31 +236,18 @@ class AnimeLatinoHD : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
return videoList return videoList
} }
override fun videoListSelector() = throw Exception("not used")
override fun videoUrlParse(document: Document) = throw Exception("not used")
override fun videoFromElement(element: Element) = throw Exception("not used")
override fun List<Video>.sort(): List<Video> { override fun List<Video>.sort(): List<Video> {
return try { val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
val videoSorted = this.sortedWith( val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
compareBy<Video> { it.quality.replace("[0-9]".toRegex(), "") }.thenByDescending { getNumberFromString(it.quality) }, val lang = preferences.getString(PREF_LANGUAGE_KEY, PREF_LANGUAGE_DEFAULT)!!
).toTypedArray() return this.sortedWith(
val userPreferredQuality = preferences.getString("preferred_quality", "[Sub] Okru:720p") compareBy(
val preferredIdx = videoSorted.indexOfFirst { x -> x.quality == userPreferredQuality } { it.quality.contains(lang) },
if (preferredIdx != -1) { { it.quality.contains(server, true) },
videoSorted.drop(preferredIdx + 1) { it.quality.contains(quality) },
videoSorted[0] = videoSorted[preferredIdx] { Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 },
} ),
videoSorted.toList() ).reversed()
} catch (e: Exception) {
this
}
}
private fun getNumberFromString(epsStr: String): String {
return epsStr.filter { it.isDigit() }.ifEmpty { "0" }
} }
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
@ -268,10 +272,6 @@ class AnimeLatinoHD : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
TypeFilter(), TypeFilter(),
) )
override fun searchAnimeFromElement(element: Element): SAnime {
return popularAnimeFromElement(element)
}
override fun searchAnimeParse(response: Response): AnimesPage { override fun searchAnimeParse(response: Response): AnimesPage {
val document = response.asJsoup() val document = response.asJsoup()
val animeList = mutableListOf<SAnime>() val animeList = mutableListOf<SAnime>()
@ -296,10 +296,6 @@ class AnimeLatinoHD : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
return AnimesPage(animeList, hasNextPage) return AnimesPage(animeList, hasNextPage)
} }
override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector()
override fun searchAnimeSelector(): String = popularAnimeSelector()
private class GenreFilter : UriPartFilter( private class GenreFilter : UriPartFilter(
"Géneros", "Géneros",
arrayOf( arrayOf(
@ -390,32 +386,13 @@ class AnimeLatinoHD : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} }
} }
override fun latestUpdatesSelector() = popularAnimeSelector()
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/animes?page=$page&status=1")
override fun latestUpdatesParse(response: Response) = popularAnimeParse(response)
override fun latestUpdatesFromElement(element: Element) = popularAnimeFromElement(element)
override fun latestUpdatesNextPageSelector() = popularAnimeNextPageSelector()
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
val options = arrayOf( ListPreference(screen.context).apply {
"[Sub] Okru:1080p", "[Sub] Okru:720p", "[Sub] Okru:480p", "[Sub] Okru:360p", "[Sub] Okru:240p", // Okru [Sub] key = PREF_LANGUAGE_KEY
"[Lat] Okru:1080p", "[Lat] Okru:720p", "[Lat] Okru:480p", "[Lat] Okru:360p", "[Lat] Okru:240p", // Okru [Lat] title = "Preferred language"
"[Sub] StreamTape", "[Lat] StreamTape", // video servers without resolution entries = LANGUAGE_LIST
"[Sub] DoodStream", "[Lat] DoodStream", // video servers without resolution entryValues = LANGUAGE_LIST
"[Sub] SolidFiles", "[Lat] SolidFiles", // video servers without resolution setDefaultValue(PREF_LANGUAGE_DEFAULT)
"[Sub] Od.lk", "[Lat] Od.lk", // video servers without resolution
"[Sub] CldUp", "[Lat] CldUp",
) // video servers without resolution
val videoQualityPref = ListPreference(screen.context).apply {
key = "preferred_quality"
title = "Preferred quality"
entries = options
entryValues = options
setDefaultValue("[Sub] Okru:720p")
summary = "%s" summary = "%s"
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
@ -424,7 +401,38 @@ class AnimeLatinoHD : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val entry = entryValues[index] as String val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit() preferences.edit().putString(key, entry).commit()
} }
}.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY
title = "Preferred quality"
entries = QUALITY_LIST
entryValues = QUALITY_LIST
setDefaultValue(PREF_QUALITY_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
} }
screen.addPreference(videoQualityPref) }.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_SERVER_KEY
title = "Preferred server"
entries = SERVER_LIST
entryValues = SERVER_LIST
setDefaultValue(PREF_SERVER_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}.also(screen::addPreference)
} }
} }

View File

@ -6,7 +6,7 @@ ext {
extName = 'AnimeMovil' extName = 'AnimeMovil'
pkgNameSuffix = 'es.animemovil' pkgNameSuffix = 'es.animemovil'
extClass = '.AnimeMovil' extClass = '.AnimeMovil'
extVersionCode = 3 extVersionCode = 4
libVersion = '13' libVersion = '13'
} }

View File

@ -58,6 +58,21 @@ class AnimeMovil : ConfigurableAnimeSource, AnimeHttpSource() {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
} }
companion object {
private const val PREF_QUALITY_KEY = "preferred_quality"
private const val PREF_QUALITY_DEFAULT = "1080"
private val QUALITY_LIST = arrayOf("1080", "720", "480", "360")
private const val PREF_SERVER_KEY = "preferred_server"
private const val PREF_SERVER_DEFAULT = "Voe"
private val SERVER_LIST = arrayOf(
"PlusTube", "PlusVid", "PlusIm", "PlusWish", "PlusHub", "PlusDex",
"YourUpload", "Voe", "StreamWish", "Mp4Upload", "Doodstream",
"Uqload", "BurstCloud", "Upstream", "StreamTape", "PlusFilm",
"Fastream", "FileLions"
)
}
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/directorio/?p=$page", headers) override fun popularAnimeRequest(page: Int) = GET("$baseUrl/directorio/?p=$page", headers)
override fun popularAnimeParse(response: Response): AnimesPage { override fun popularAnimeParse(response: Response): AnimesPage {
@ -203,7 +218,7 @@ class AnimeMovil : ConfigurableAnimeSource, AnimeHttpSource() {
videoList.add(Video(fileSrc, "$serverName:HLS", fileSrc, headers = null)) videoList.add(Video(fileSrc, "$serverName:HLS", fileSrc, headers = null))
} }
if (fileSrc.contains(".mp4")) { if (fileSrc.contains(".mp4")) {
videoList.add(Video(fileSrc, serverName, fileSrc, headers = null)) videoList.add(Video(fileSrc, "$serverName:MP4", fileSrc, headers = null))
} }
} }
} else { } else {
@ -222,65 +237,62 @@ class AnimeMovil : ConfigurableAnimeSource, AnimeHttpSource() {
val embedUrl = url.lowercase() val embedUrl = url.lowercase()
try { try {
if (embedUrl.contains("voe")) { if (embedUrl.contains("voe")) {
VoeExtractor(client).videoFromUrl(url, "Voe")?.let { videoList.add(it) } VoeExtractor(client).videoFromUrl(url, prefix = "Voe:")?.let { videoList.add(it) }
} }
if (embedUrl.contains("filemoon") || embedUrl.contains("moonplayer")) { if (embedUrl.contains("filemoon") || embedUrl.contains("moonplayer")) {
FilemoonExtractor(client).videosFromUrl(url).also(videoList::addAll) FilemoonExtractor(client).videosFromUrl(url, prefix = "Filemoon:").also(videoList::addAll)
} }
if (embedUrl.contains("uqload")) { if (embedUrl.contains("uqload")) {
UqloadExtractor(client).videosFromUrl(url) UqloadExtractor(client).videosFromUrl(url).also(videoList::addAll)
} }
if (embedUrl.contains("mp4upload")) { if (embedUrl.contains("mp4upload")) {
val newHeaders = headers.newBuilder().add("referer", "https://re.animepelix.net/").build() val newHeaders = headers.newBuilder().add("referer", "https://re.animepelix.net/").build()
Mp4uploadExtractor(client).videosFromUrl(url, newHeaders).let { videoList.addAll(it) } Mp4uploadExtractor(client).videosFromUrl(url, newHeaders).also(videoList::addAll)
} }
if (embedUrl.contains("wishembed") || embedUrl.contains("streamwish") || embedUrl.contains("wish")) { if (embedUrl.contains("wishembed") || embedUrl.contains("streamwish") || embedUrl.contains("wish")) {
val docHeaders = headers.newBuilder() val docHeaders = headers.newBuilder()
.add("Referer", "$baseUrl/") .add("Referer", "$baseUrl/")
.build() .build()
StreamWishExtractor(client, docHeaders).videosFromUrl(url, "StreamWish") StreamWishExtractor(client, docHeaders).videosFromUrl(url, videoNameGen = { "StreamWish:$it" }).also(videoList::addAll)
} }
if (embedUrl.contains("doodstream") || embedUrl.contains("dood.")) { if (embedUrl.contains("doodstream") || embedUrl.contains("dood.")) {
DoodExtractor(client).videoFromUrl(url, "DoodStream", false)?.let { videoList.add(it) } DoodExtractor(client).videoFromUrl(url, "DoodStream", false)?.let { videoList.add(it) }
} }
if (embedUrl.contains("streamlare")) { if (embedUrl.contains("streamlare")) {
videoList.addAll(StreamlareExtractor(client).videosFromUrl(url)) StreamlareExtractor(client).videosFromUrl(url).also(videoList::addAll)
} }
if (embedUrl.contains("yourupload")) { if (embedUrl.contains("yourupload")) {
YourUploadExtractor(client).videoFromUrl(url, headers = headers).let { videoList.addAll(it) } YourUploadExtractor(client).videoFromUrl(url, headers = headers).also(videoList::addAll)
} }
if (embedUrl.contains("burstcloud") || embedUrl.contains("burst")) { if (embedUrl.contains("burstcloud") || embedUrl.contains("burst")) {
BurstCloudExtractor(client).videoFromUrl(url, headers = headers).let { videoList.addAll(it) } BurstCloudExtractor(client).videoFromUrl(url, headers = headers).also(videoList::addAll)
} }
if (embedUrl.contains("fastream")) { if (embedUrl.contains("fastream")) {
FastreamExtractor(client).videoFromUrl(url).forEach { videoList.add(it) } FastreamExtractor(client).videoFromUrl(url).also(videoList::addAll)
} }
if (embedUrl.contains("upstream")) { if (embedUrl.contains("upstream")) {
UpstreamExtractor(client).videosFromUrl(url).let { videoList.addAll(it) } UpstreamExtractor(client).videosFromUrl(url).also(videoList::addAll)
} }
if (embedUrl.contains("streamtape")) { if (embedUrl.contains("streamtape")) {
StreamTapeExtractor(client).videoFromUrl(url)?.let { videoList.add(it) } StreamTapeExtractor(client).videoFromUrl(url)?.also(videoList::add)
}
if (embedUrl.contains("filelions") || embedUrl.contains("lion")) {
StreamWishExtractor(client, headers).videosFromUrl(url, videoNameGen = { "FileLions:$it" }).also(videoList::addAll)
} }
} catch (_: Exception) {} } catch (_: Exception) {}
return videoList return videoList
} }
override fun List<Video>.sort(): List<Video> { override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString("preferred_quality", "Voe") val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
if (quality != null) { val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
val newList = mutableListOf<Video>() return this.sortedWith(
var preferred = 0 compareBy(
for (video in this) { { it.quality.contains(server, true) },
if (video.quality == quality) { { it.quality.contains(quality) },
newList.add(preferred, video) { Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 },
preferred++ ),
} else { ).reversed()
newList.add(video)
}
}
return newList
}
return this
} }
override fun getFilterList(): AnimeFilterList = AnimeFilterList( override fun getFilterList(): AnimeFilterList = AnimeFilterList(
@ -396,19 +408,12 @@ class AnimeMovil : ConfigurableAnimeSource, AnimeHttpSource() {
} }
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
val qualities = arrayOf( ListPreference(screen.context).apply {
"PlusTube", "PlusVid", "PlusIm", "PlusWish", "PlusHub", "PlusDex", key = PREF_QUALITY_KEY
"YourUpload", "BurstCloud", "Voe", "StreamWish", "Mp4Upload", "Doodstream",
"Upload", "BurstCloud", "Upstream", "StreamTape", "PlusFilm", "Doodstream",
"Fastream:1080p", "Fastream:720p", "Fastream:480p", "Fastream:360p",
"Filemoon:1080p", "Filemoon:720p", "Filemoon:480p", "Filemoon:360p",
)
val videoQualityPref = ListPreference(screen.context).apply {
key = "preferred_quality"
title = "Preferred quality" title = "Preferred quality"
entries = qualities entries = QUALITY_LIST
entryValues = qualities entryValues = QUALITY_LIST
setDefaultValue("Voe") setDefaultValue(PREF_QUALITY_DEFAULT)
summary = "%s" summary = "%s"
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
@ -417,8 +422,23 @@ class AnimeMovil : ConfigurableAnimeSource, AnimeHttpSource() {
val entry = entryValues[index] as String val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit() preferences.edit().putString(key, entry).commit()
} }
}.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_SERVER_KEY
title = "Preferred server"
entries = SERVER_LIST
entryValues = SERVER_LIST
setDefaultValue(PREF_SERVER_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
} }
screen.addPreference(videoQualityPref) }.also(screen::addPreference)
} }
@Serializable @Serializable

View File

@ -5,7 +5,7 @@ ext {
extName = 'Animeyt' extName = 'Animeyt'
pkgNameSuffix = 'es.animeyt' pkgNameSuffix = 'es.animeyt'
extClass = '.Animeyt' extClass = '.Animeyt'
extVersionCode = 7 extVersionCode = 8
libVersion = '13' libVersion = '13'
} }
dependencies { dependencies {

View File

@ -13,7 +13,6 @@ import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
import eu.kanade.tachiyomi.lib.fastreamextractor.FastreamExtractor import eu.kanade.tachiyomi.lib.fastreamextractor.FastreamExtractor
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.json.Json
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
@ -21,12 +20,11 @@ import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.lang.Exception import java.lang.Exception
class Animeyt : ConfigurableAnimeSource, ParsedAnimeHttpSource() { class Animeyt : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val name = "Animeyt" override val name = "AnimeYT"
override val baseUrl = "https://ytanime.tv" override val baseUrl = "https://ytanime.tv"
@ -36,15 +34,23 @@ class Animeyt : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val client: OkHttpClient = network.cloudflareClient override val client: OkHttpClient = network.cloudflareClient
private val json: Json by injectLazy()
private val preferences: SharedPreferences by lazy { private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
} }
companion object {
private const val PREF_QUALITY_KEY = "preferred_quality"
private const val PREF_QUALITY_DEFAULT = "1080"
private val QUALITY_LIST = arrayOf("1080", "720", "480", "360")
private const val PREF_SERVER_KEY = "preferred_server"
private const val PREF_SERVER_DEFAULT = "Fastream"
private val SERVER_LIST = arrayOf("Fastream")
}
override fun popularAnimeSelector(): String = "div.video-block div.row div.col-md-2 div.video-card" override fun popularAnimeSelector(): String = "div.video-block div.row div.col-md-2 div.video-card"
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/ultimos-animes?page=$page") override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/mas-populares?page=$page")
override fun popularAnimeFromElement(element: Element): SAnime { override fun popularAnimeFromElement(element: Element): SAnime {
val anime = SAnime.create() val anime = SAnime.create()
@ -83,8 +89,13 @@ class Animeyt : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
.split(".")[0] .split(".")[0]
.replace("https://", "") .replace("https://", "")
.replace("http://", "") .replace("http://", "")
val url = container.attr("src")
var url = container.attr("src")
if (server == "fastream") { if (server == "fastream") {
if (url.contains("emb.html")) {
val key = url.split("/").last()
url = "https://fastream.to/embed-$key.html"
}
FastreamExtractor(client).videoFromUrl(url).forEach { videoList.add(it) } FastreamExtractor(client).videoFromUrl(url).forEach { videoList.add(it) }
} }
} }
@ -98,24 +109,15 @@ class Animeyt : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun videoFromElement(element: Element) = throw Exception("not used") override fun videoFromElement(element: Element) = throw Exception("not used")
override fun List<Video>.sort(): List<Video> { override fun List<Video>.sort(): List<Video> {
return try { val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
val videoSorted = this.sortedWith( val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
compareBy<Video> { it.quality.replace("[0-9]".toRegex(), "") }.thenByDescending { getNumberFromString(it.quality) }, return this.sortedWith(
).toTypedArray() compareBy(
val userPreferredQuality = preferences.getString("preferred_quality", "Fastream:720p") { it.quality.contains(server, true) },
val preferredIdx = videoSorted.indexOfFirst { x -> x.quality == userPreferredQuality } { it.quality.contains(quality) },
if (preferredIdx != -1) { { Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 },
videoSorted.drop(preferredIdx + 1) ),
videoSorted[0] = videoSorted[preferredIdx] ).reversed()
}
videoSorted.toList()
} catch (e: Exception) {
this
}
}
private fun getNumberFromString(epsStr: String): String {
return epsStr.filter { it.isDigit() }.ifEmpty { "0" }
} }
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request = GET("$baseUrl/search?q=$query&page=$page") override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request = GET("$baseUrl/search?q=$query&page=$page")
@ -150,17 +152,17 @@ class Animeyt : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun latestUpdatesFromElement(element: Element) = popularAnimeFromElement(element) override fun latestUpdatesFromElement(element: Element) = popularAnimeFromElement(element)
override fun latestUpdatesRequest(page: Int) = popularAnimeRequest(page) override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/ultimos-animes?page=$page")
override fun latestUpdatesSelector() = popularAnimeSelector() override fun latestUpdatesSelector() = popularAnimeSelector()
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
val videoQualityPref = ListPreference(screen.context).apply { ListPreference(screen.context).apply {
key = "preferred_quality" key = PREF_QUALITY_KEY
title = "Preferred quality" title = "Preferred quality"
entries = arrayOf("Fastream:720p", "Fastream:480p", "Fastream:360p") entries = QUALITY_LIST
entryValues = arrayOf("Fastream:720p", "Fastream:480p", "Fastream:360p") entryValues = QUALITY_LIST
setDefaultValue("Fastream:720p") setDefaultValue(PREF_QUALITY_DEFAULT)
summary = "%s" summary = "%s"
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
@ -169,7 +171,22 @@ class Animeyt : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val entry = entryValues[index] as String val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit() preferences.edit().putString(key, entry).commit()
} }
}.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_SERVER_KEY
title = "Preferred server"
entries = SERVER_LIST
entryValues = SERVER_LIST
setDefaultValue(PREF_SERVER_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
} }
screen.addPreference(videoQualityPref) }.also(screen::addPreference)
} }
} }

View File

@ -5,12 +5,28 @@ ext {
extName = 'AsiaLiveAction' extName = 'AsiaLiveAction'
pkgNameSuffix = 'es.asialiveaction' pkgNameSuffix = 'es.asialiveaction'
extClass = '.AsiaLiveAction' extClass = '.AsiaLiveAction'
extVersionCode = 21 extVersionCode = 22
libVersion = '13' libVersion = '13'
} }
dependencies { dependencies {
implementation(project(':lib-vudeo-extractor'))
implementation(project(':lib-uqload-extractor'))
implementation(project(':lib-streamwish-extractor'))
implementation(project(':lib-filemoon-extractor'))
implementation(project(':lib-streamlare-extractor'))
implementation(project(':lib-yourupload-extractor'))
implementation(project(':lib-streamtape-extractor'))
implementation(project(':lib-dood-extractor'))
implementation(project(':lib-voe-extractor'))
implementation(project(':lib-okru-extractor')) implementation(project(':lib-okru-extractor'))
implementation(project(':lib-mp4upload-extractor'))
implementation(project(':lib-mixdrop-extractor'))
implementation(project(':lib-burstcloud-extractor'))
implementation(project(':lib-fastream-extractor'))
implementation(project(':lib-upstream-extractor'))
implementation(project(':lib-streamhidevid-extractor'))
implementation(project(':lib-vk-extractor'))
} }

View File

@ -4,6 +4,7 @@ import android.app.Application
import android.content.SharedPreferences import android.content.SharedPreferences
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.es.asialiveaction.extractors.VidGuardExtractor
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.animesource.model.AnimeFilter import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
@ -11,9 +12,24 @@ import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor import eu.kanade.tachiyomi.lib.burstcloudextractor.BurstCloudExtractor
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
import eu.kanade.tachiyomi.lib.fastreamextractor.FastreamExtractor
import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor
import eu.kanade.tachiyomi.lib.streamhidevidextractor.StreamHideVidExtractor
import eu.kanade.tachiyomi.lib.streamlareextractor.StreamlareExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
import eu.kanade.tachiyomi.lib.upstreamextractor.UpstreamExtractor
import eu.kanade.tachiyomi.lib.uqloadextractor.UqloadExtractor
import eu.kanade.tachiyomi.lib.vkextractor.VkExtractor
import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.json.Json
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
@ -21,7 +37,7 @@ import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.lang.Exception import uy.kohesive.injekt.injectLazy
import java.util.Calendar import java.util.Calendar
class AsiaLiveAction : ConfigurableAnimeSource, ParsedAnimeHttpSource() { class AsiaLiveAction : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
@ -36,10 +52,28 @@ class AsiaLiveAction : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val client: OkHttpClient = network.cloudflareClient override val client: OkHttpClient = network.cloudflareClient
private val json: Json by injectLazy()
private val preferences: SharedPreferences by lazy { private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
} }
companion object {
private const val PREF_QUALITY_KEY = "preferred_quality"
private const val PREF_QUALITY_DEFAULT = "1080"
private val QUALITY_LIST = arrayOf("1080", "720", "480", "360")
private const val PREF_SERVER_KEY = "preferred_server"
private const val PREF_SERVER_DEFAULT = "FileLions"
private val SERVER_LIST = arrayOf(
"YourUpload", "Voe", "Mp4Upload", "Doodstream",
"Upload", "BurstCloud", "Upstream", "StreamTape",
"Fastream", "Filemoon", "StreamWish", "VidGuard",
"Amazon", "AmazonES", "Fireload", "FileLions",
"vk.com",
)
}
override fun popularAnimeSelector(): String = "div.TpRwCont main section ul.MovieList li.TPostMv article.TPost" override fun popularAnimeSelector(): String = "div.TpRwCont main section ul.MovieList li.TPostMv article.TPost"
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/todos/page/$page") override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/todos/page/$page")
@ -58,7 +92,7 @@ class AsiaLiveAction : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val anime = SAnime.create() val anime = SAnime.create()
anime.thumbnail_url = document.selectFirst("header div.Image figure img")!!.attr("src").trim().replace("//", "https://") anime.thumbnail_url = document.selectFirst("header div.Image figure img")!!.attr("src").trim().replace("//", "https://")
anime.title = document.selectFirst("header div.asia-post-header h1.Title")!!.text() anime.title = document.selectFirst("header div.asia-post-header h1.Title")!!.text()
anime.description = document.selectFirst("header div.asia-post-main div.Description p:nth-child(2)")!!.text().removeSurrounding("\"") anime.description = document.selectFirst("header div.asia-post-main div.Description p:nth-child(2), header div.asia-post-main div.Description p")!!.text().removeSurrounding("\"")
anime.genre = document.select("div.asia-post-main p.Info span.tags a").joinToString { it.text() } anime.genre = document.select("div.asia-post-main p.Info span.tags a").joinToString { it.text() }
val year = document.select("header div.asia-post-main p.Info span.Date a").text().toInt() val year = document.select("header div.asia-post-main p.Info span.Date a").text().toInt()
val currentYear = Calendar.getInstance().get(Calendar.YEAR) val currentYear = Calendar.getInstance().get(Calendar.YEAR)
@ -74,38 +108,137 @@ class AsiaLiveAction : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
return super.episodeListParse(response).reversed() return super.episodeListParse(response).reversed()
} }
override fun episodeListSelector() = "#ep-list div.TPTblCn span a" override fun episodeListSelector() = "#ep-list div.TPTblCn span a, #ep-list div.TPTblCn .accordion"
override fun episodeFromElement(element: Element): SEpisode { override fun episodeFromElement(element: Element): SEpisode {
val episode = SEpisode.create() return if (element.attr("class").contains("accordion")) {
val epNum = getNumberFromEpsString(element.select("div.flex-grow-1 p").text()) val epNum = getNumberFromEpsString(element.select("label span").text())
episode.setUrlWithoutDomain(element.attr("href")) SEpisode.create().apply {
episode.episode_number = when { name = element.select("label span").text().trim()
episode_number = when {
epNum.isNotEmpty() -> epNum.toFloatOrNull() ?: 1F epNum.isNotEmpty() -> epNum.toFloatOrNull() ?: 1F
else -> 1F else -> 1F
} }
episode.name = element.select("div.flex-grow-1 p").text().trim() setUrlWithoutDomain(element.selectFirst("ul li a")?.attr("abs:href")!!)
return episode }
} else {
val epNum = getNumberFromEpsString(element.select("div.flex-grow-1 p").text())
SEpisode.create().apply {
setUrlWithoutDomain(element.attr("abs:href"))
episode_number = when {
epNum.isNotEmpty() -> epNum.toFloatOrNull() ?: 1F
else -> 1F
}
name = element.select("div.flex-grow-1 p").text().trim()
}
}
} }
private fun getNumberFromEpsString(epsStr: String): String { private fun getNumberFromEpsString(epsStr: String): String {
return epsStr.filter { it.isDigit() } return epsStr.filter { it.isDigit() }
} }
private fun fetchUrls(text: String?): List<String> {
if (text.isNullOrEmpty()) return listOf()
val linkRegex = "(http|ftp|https):\\/\\/([\\w_-]+(?:(?:\\.[\\w_-]+)+))([\\w.,@?^=%&:\\/~+#-]*[\\w@?^=%&\\/~+#-])".toRegex()
return linkRegex.findAll(text).map { it.value.trim().removeSurrounding("\"") }.toList()
}
override fun videoListParse(response: Response): List<Video> { override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup() val document = response.asJsoup()
val videoList = mutableListOf<Video>() val videoList = mutableListOf<Video>()
document.select("script").forEach { script -> document.select("script:containsData(var videos)").forEach { script ->
if (script.data().contains("var videosJap = [") || script.data().contains("var videosCor = [")) { fetchUrls(script.data()).map { url ->
val content = script.data() try {
serverVideoResolver(url).also(videoList::addAll)
} catch (_: Exception) {}
}
}
return videoList
}
if (content.contains("okru")) { private fun serverVideoResolver(url: String): List<Video> {
val url = content.substringAfter(",['OK','").substringBefore("',0,0]") val videoList = mutableListOf<Video>()
val videos = OkruExtractor(client).videosFromUrl(url) val embedUrl = url.lowercase()
videoList.addAll(videos) try {
if (embedUrl.contains("voe")) {
VoeExtractor(client).videoFromUrl(url, prefix = "Voe:")?.let { videoList.add(it) }
}
if ((embedUrl.contains("amazon") || embedUrl.contains("amz")) && !embedUrl.contains("disable")) {
val body = client.newCall(GET(url)).execute().asJsoup()
if (body.select("script:containsData(var shareId)").toString().isNotBlank()) {
val shareId = body.selectFirst("script:containsData(var shareId)")!!.data()
.substringAfter("shareId = \"").substringBefore("\"")
val amazonApiJson = client.newCall(GET("https://www.amazon.com/drive/v1/shares/$shareId?resourceVersion=V2&ContentType=JSON&asset=ALL"))
.execute().asJsoup()
val epId = amazonApiJson.toString().substringAfter("\"id\":\"").substringBefore("\"")
val amazonApi =
client.newCall(GET("https://www.amazon.com/drive/v1/nodes/$epId/children?resourceVersion=V2&ContentType=JSON&limit=200&sort=%5B%22kind+DESC%22%2C+%22modifiedDate+DESC%22%5D&asset=ALL&tempLink=true&shareId=$shareId"))
.execute().asJsoup()
val videoUrl = amazonApi.toString().substringAfter("\"FOLDER\":").substringAfter("tempLink\":\"").substringBefore("\"")
videoList.add(Video(videoUrl, "Amazon", videoUrl))
} }
} }
if (embedUrl.contains("filemoon") || embedUrl.contains("moonplayer")) {
val vidHeaders = headers.newBuilder()
.add("Origin", "https://${url.toHttpUrl().host}")
.add("Referer", "https://${url.toHttpUrl().host}/")
.build()
FilemoonExtractor(client).videosFromUrl(url, prefix = "Filemoon:", headers = vidHeaders).also(videoList::addAll)
} }
if (embedUrl.contains("uqload")) {
UqloadExtractor(client).videosFromUrl(url).also(videoList::addAll)
}
if (embedUrl.contains("mp4upload")) {
Mp4uploadExtractor(client).videosFromUrl(url, headers).let { videoList.addAll(it) }
}
if (embedUrl.contains("wishembed") ||
embedUrl.contains("streamwish") ||
embedUrl.contains("strwish") ||
embedUrl.contains("wish") ||
embedUrl.contains("sfastwish")
) {
val docHeaders = headers.newBuilder()
.add("Origin", "https://${url.toHttpUrl().host}")
.add("Referer", "https://${url.toHttpUrl().host}/")
.build()
StreamWishExtractor(client, docHeaders).videosFromUrl(url, videoNameGen = { "StreamWish:$it" }).also(videoList::addAll)
}
if (embedUrl.contains("doodstream") || embedUrl.contains("dood.")) {
val url2 = url.replace("https://doodstream.com/e/", "https://dood.to/e/")
DoodExtractor(client).videoFromUrl(url2, "DoodStream", false)?.let { videoList.add(it) }
}
if (embedUrl.contains("streamlare")) {
StreamlareExtractor(client).videosFromUrl(url).let { videoList.addAll(it) }
}
if (embedUrl.contains("yourupload") || embedUrl.contains("upload")) {
YourUploadExtractor(client).videoFromUrl(url, headers = headers).let { videoList.addAll(it) }
}
if (embedUrl.contains("burstcloud") || embedUrl.contains("burst")) {
BurstCloudExtractor(client).videoFromUrl(url, headers = headers).let { videoList.addAll(it) }
}
if (embedUrl.contains("fastream")) {
FastreamExtractor(client).videoFromUrl(url).forEach { videoList.add(it) }
}
if (embedUrl.contains("upstream")) {
UpstreamExtractor(client).videosFromUrl(url).let { videoList.addAll(it) }
}
if (embedUrl.contains("streamtape") || embedUrl.contains("stp") || embedUrl.contains("stape")) {
StreamTapeExtractor(client).videoFromUrl(url)?.let { videoList.add(it) }
}
if (embedUrl.contains("ahvsh") || embedUrl.contains("streamhide") || embedUrl.contains("hide")) {
StreamHideVidExtractor(client).videosFromUrl(url).let { videoList.addAll(it) }
}
if (embedUrl.contains("filelions") || embedUrl.contains("lion") || embedUrl.contains("fviplions")) {
StreamWishExtractor(client, headers).videosFromUrl(url, videoNameGen = { "FileLions:$it" }).also(videoList::addAll)
}
if (embedUrl.contains("vembed") || embedUrl.contains("guard")) {
VidGuardExtractor(client).videosFromUrl(url).also(videoList::addAll)
}
if (embedUrl.contains("vk")) {
VkExtractor(client, headers).videosFromUrl(url).also(videoList::addAll)
}
} catch (_: Exception) { }
return videoList return videoList
} }
@ -116,24 +249,15 @@ class AsiaLiveAction : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun videoFromElement(element: Element) = throw Exception("not used") override fun videoFromElement(element: Element) = throw Exception("not used")
override fun List<Video>.sort(): List<Video> { override fun List<Video>.sort(): List<Video> {
return try { val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
val videoSorted = this.sortedWith( val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
compareBy<Video> { it.quality.replace("[0-9]".toRegex(), "") }.thenByDescending { getNumberFromString(it.quality) }, return this.sortedWith(
).toTypedArray() compareBy(
val userPreferredQuality = preferences.getString("preferred_quality", "Okru:1080p") { it.quality.contains(server, true) },
val preferredIdx = videoSorted.indexOfFirst { x -> x.quality == userPreferredQuality } { it.quality.contains(quality) },
if (preferredIdx != -1) { { Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 },
videoSorted.drop(preferredIdx + 1) ),
videoSorted[0] = videoSorted[preferredIdx] ).reversed()
}
videoSorted.toList()
} catch (e: Exception) {
this
}
}
private fun getNumberFromString(epsStr: String): String {
return epsStr.filter { it.isDigit() }.ifEmpty { "0" }
} }
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
@ -201,19 +325,12 @@ class AsiaLiveAction : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun latestUpdatesSelector() = popularAnimeSelector() override fun latestUpdatesSelector() = popularAnimeSelector()
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
val qualities = arrayOf( ListPreference(screen.context).apply {
"Okru:1080p", key = PREF_QUALITY_KEY
"Okru:720p",
"Okru:480p",
"Okru:360p",
"Okru:240p", // Okru
)
val videoQualityPref = ListPreference(screen.context).apply {
key = "preferred_quality"
title = "Preferred quality" title = "Preferred quality"
entries = qualities entries = QUALITY_LIST
entryValues = qualities entryValues = QUALITY_LIST
setDefaultValue("Okru:1080p") setDefaultValue(PREF_QUALITY_DEFAULT)
summary = "%s" summary = "%s"
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
@ -222,7 +339,22 @@ class AsiaLiveAction : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val entry = entryValues[index] as String val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit() preferences.edit().putString(key, entry).commit()
} }
}.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_SERVER_KEY
title = "Preferred server"
entries = SERVER_LIST
entryValues = SERVER_LIST
setDefaultValue(PREF_SERVER_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
} }
screen.addPreference(videoQualityPref) }.also(screen::addPreference)
} }
} }

View File

@ -0,0 +1,124 @@
package eu.kanade.tachiyomi.animeextension.es.asialiveaction.extractors
import android.app.Application
import android.os.Handler
import android.os.Looper
import android.util.Base64
import android.webkit.JavascriptInterface
import android.webkit.WebSettings
import android.webkit.WebView
import android.webkit.WebViewClient
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import uy.kohesive.injekt.injectLazy
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
class VidGuardExtractor(private val client: OkHttpClient) {
private val context: Application by injectLazy()
private val handler by lazy { Handler(Looper.getMainLooper()) }
class JsObject(private val latch: CountDownLatch) {
var payload: String = ""
@JavascriptInterface
fun passPayload(passedPayload: String) {
payload = passedPayload
latch.countDown()
}
}
fun videosFromUrl(url: String): List<Video> {
val doc = client.newCall(GET(url)).execute().use { it.asJsoup() }
val scriptUrl = doc.selectFirst("script[src*=ad/plugin]")
?.absUrl("src")
?: return emptyList()
val headers = Headers.headersOf("Referer", url)
val script = client.newCall(GET(scriptUrl, headers)).execute()
.use { it.body.string() }
val sources = getSourcesFromScript(script, url)
.takeIf { it.isNotBlank() && it != "undefined" }
?: return emptyList()
return sources.substringAfter("stream:[").substringBefore("}]")
.split('{')
.drop(1)
.mapNotNull { line ->
val resolution = line.substringAfter("Label\":\"").substringBefore('"')
val videoUrl = line.substringAfter("URL\":\"").substringBefore('"')
.takeIf(String::isNotBlank)
?.let(::fixUrl)
?: return@mapNotNull null
Video(videoUrl, "VidGuard:$resolution", videoUrl, headers)
}
}
private fun getSourcesFromScript(script: String, url: String): String {
val latch = CountDownLatch(1)
var webView: WebView? = null
val jsinterface = JsObject(latch)
handler.post {
val webview = WebView(context)
webView = webview
with(webview.settings) {
javaScriptEnabled = true
domStorageEnabled = true
databaseEnabled = true
useWideViewPort = false
loadWithOverviewMode = false
cacheMode = WebSettings.LOAD_NO_CACHE
}
webview.addJavascriptInterface(jsinterface, "android")
webview.webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
view?.clearCache(true)
view?.clearFormData()
view?.evaluateJavascript(script) {}
view?.evaluateJavascript("window.android.passPayload(JSON.stringify(window.svg))") {}
}
}
webview.loadDataWithBaseURL(url, "<html></html>", "text/html", "UTF-8", null)
}
latch.await(5, TimeUnit.SECONDS)
handler.post {
webView?.stopLoading()
webView?.destroy()
webView = null
}
return jsinterface.payload
}
private fun fixUrl(url: String): String {
val httpUrl = url.toHttpUrl()
val originalSign = httpUrl.queryParameter("sig")!!
val newSign = originalSign.chunked(2).joinToString("") {
Char(it.toInt(16) xor 2).toString()
}
.let { String(Base64.decode(it, Base64.DEFAULT)) }
.substring(5)
.chunked(2)
.reversed()
.joinToString("")
.substring(5)
return httpUrl.newBuilder()
.removeAllQueryParameters("sig")
.addQueryParameter("sig", newSign)
.build()
.toString()
}
}

View File

@ -6,18 +6,26 @@ ext {
extName = 'Cuevana' extName = 'Cuevana'
pkgNameSuffix = 'es.cuevana' pkgNameSuffix = 'es.cuevana'
extClass = '.CuevanaFactory' extClass = '.CuevanaFactory'
extVersionCode = 23 extVersionCode = 24
libVersion = '13' libVersion = '13'
} }
dependencies { dependencies {
implementation(project(':lib-vudeo-extractor'))
implementation(project(':lib-uqload-extractor'))
implementation(project(':lib-streamwish-extractor')) implementation(project(':lib-streamwish-extractor'))
implementation(project(':lib-filemoon-extractor'))
implementation(project(':lib-streamlare-extractor'))
implementation(project(':lib-yourupload-extractor')) implementation(project(':lib-yourupload-extractor'))
implementation(project(':lib-streamtape-extractor'))
implementation(project(':lib-dood-extractor')) implementation(project(':lib-dood-extractor'))
implementation project(path: ':lib-okru-extractor') implementation(project(':lib-voe-extractor'))
implementation project(path: ':lib-voe-extractor') implementation(project(':lib-okru-extractor'))
implementation project(path: ':lib-streamtape-extractor') implementation(project(':lib-mp4upload-extractor'))
implementation project(path: ':lib-filemoon-extractor') implementation(project(':lib-mixdrop-extractor'))
implementation(project(':lib-burstcloud-extractor'))
implementation(project(':lib-fastream-extractor'))
implementation(project(':lib-upstream-extractor'))
implementation "dev.datlag.jsunpacker:jsunpacker:1.0.1" implementation "dev.datlag.jsunpacker:jsunpacker:1.0.1"
} }

View File

@ -11,10 +11,17 @@ import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
import eu.kanade.tachiyomi.lib.burstcloudextractor.BurstCloudExtractor
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
import eu.kanade.tachiyomi.lib.fastreamextractor.FastreamExtractor
import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
import eu.kanade.tachiyomi.lib.streamlareextractor.StreamlareExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
import eu.kanade.tachiyomi.lib.upstreamextractor.UpstreamExtractor
import eu.kanade.tachiyomi.lib.uqloadextractor.UqloadExtractor
import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
@ -22,6 +29,7 @@ import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.jsonPrimitive
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
@ -46,6 +54,25 @@ class CuevanaCh(override val name: String, override val baseUrl: String) : Confi
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
} }
companion object {
private const val PREF_LANGUAGE_KEY = "preferred_language"
private const val PREF_LANGUAGE_DEFAULT = "[LAT]"
private val LANGUAGE_LIST = arrayOf("[LAT]", "[SUB]", "[CAST]")
private const val PREF_QUALITY_KEY = "preferred_quality"
private const val PREF_QUALITY_DEFAULT = "1080"
private val QUALITY_LIST = arrayOf("1080", "720", "480", "360")
private const val PREF_SERVER_KEY = "preferred_server"
private const val PREF_SERVER_DEFAULT = "Voe"
private val SERVER_LIST = arrayOf(
"YourUpload", "BurstCloud", "Voe", "Mp4Upload", "Doodstream",
"Upload", "BurstCloud", "Upstream", "StreamTape", "Amazon",
"Fastream", "Filemoon", "StreamWish", "Okru", "Streamlare",
"Tomatomatela",
)
}
override fun popularAnimeSelector(): String = ".MovieList .TPostMv .TPost" override fun popularAnimeSelector(): String = ".MovieList .TPostMv .TPost"
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/peliculas?page=$page") override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/peliculas?page=$page")
@ -71,10 +98,18 @@ class CuevanaCh(override val name: String, override val baseUrl: String) : Confi
idxSeason idxSeason
} }
season.select(".TPostMv article.TPost").reversed().mapIndexed { idxCap, cap -> season.select(".TPostMv article.TPost").reversed().mapIndexed { idxCap, cap ->
val epNum = try { cap.select("a div.Image span.Year").text().substringAfter("x").toFloat() } catch (e: Exception) { idxCap.toFloat() } val epNum = try {
cap.select("a div.Image span.Year").text().substringAfter("x").toFloat()
} catch (e: Exception) {
idxCap.toFloat()
}
val episode = SEpisode.create() val episode = SEpisode.create()
val date = cap.select("a > p").text() val date = cap.select("a > p").text()
val epDate = try { SimpleDateFormat("yyyy-MM-dd").parse(date) } catch (e: Exception) { null } val epDate = try {
SimpleDateFormat("yyyy-MM-dd").parse(date)
} catch (e: Exception) {
null
}
episode.episode_number = epNum episode.episode_number = epNum
episode.name = "T$noSeason - Episodio $epNum" episode.name = "T$noSeason - Episodio $epNum"
if (epDate != null) episode.date_upload = epDate.time if (epDate != null) episode.date_upload = epDate.time
@ -112,21 +147,87 @@ class CuevanaCh(override val name: String, override val baseUrl: String) : Confi
} else { } else {
"" ""
} }
} catch (e: Exception) { "" } } catch (e: Exception) {
""
}
val url = it.attr("abs:data-video") val url = it.attr("abs:data-video")
try { try {
loadExtractor(url, langPrefix).map { video -> videoList.add(video) } serverVideoResolver(url, langPrefix).also(videoList::addAll)
} catch (_: Exception) { } } catch (_: Exception) { }
} }
return videoList return videoList
} }
private fun loadExtractor(url: String, prefix: String = ""): List<Video> { private fun serverVideoResolver(url: String, prefix: String = ""): List<Video> {
val videoList = mutableListOf<Video>() val videoList = mutableListOf<Video>()
val embedUrl = url.lowercase() val embedUrl = url.lowercase()
if (embedUrl.contains("tomatomatela")) {
try { try {
if (embedUrl.contains("voe")) {
VoeExtractor(client).videoFromUrl(url, prefix = "$prefix Voe:")?.let { videoList.add(it) }
}
if ((embedUrl.contains("amazon") || embedUrl.contains("amz")) && !embedUrl.contains("disable")) {
val body = client.newCall(GET(url)).execute().asJsoup()
if (body.select("script:containsData(var shareId)").toString().isNotBlank()) {
val shareId = body.selectFirst("script:containsData(var shareId)")!!.data()
.substringAfter("shareId = \"").substringBefore("\"")
val amazonApiJson = client.newCall(GET("https://www.amazon.com/drive/v1/shares/$shareId?resourceVersion=V2&ContentType=JSON&asset=ALL"))
.execute().asJsoup()
val epId = amazonApiJson.toString().substringAfter("\"id\":\"").substringBefore("\"")
val amazonApi =
client.newCall(GET("https://www.amazon.com/drive/v1/nodes/$epId/children?resourceVersion=V2&ContentType=JSON&limit=200&sort=%5B%22kind+DESC%22%2C+%22modifiedDate+DESC%22%5D&asset=ALL&tempLink=true&shareId=$shareId"))
.execute().asJsoup()
val videoUrl = amazonApi.toString().substringAfter("\"FOLDER\":").substringAfter("tempLink\":\"").substringBefore("\"")
videoList.add(Video(videoUrl, "$prefix Amazon", videoUrl))
}
}
if (embedUrl.contains("ok.ru") || embedUrl.contains("okru")) {
OkruExtractor(client).videosFromUrl(url, prefix).also(videoList::addAll)
}
if (embedUrl.contains("filemoon") || embedUrl.contains("moonplayer")) {
val vidHeaders = headers.newBuilder()
.add("Origin", "https://${url.toHttpUrl().host}")
.add("Referer", "https://${url.toHttpUrl().host}/")
.build()
FilemoonExtractor(client).videosFromUrl(url, prefix = "$prefix Filemoon:", headers = vidHeaders).also(videoList::addAll)
}
if (embedUrl.contains("uqload")) {
UqloadExtractor(client).videosFromUrl(url, prefix = prefix).also(videoList::addAll)
}
if (embedUrl.contains("mp4upload")) {
Mp4uploadExtractor(client).videosFromUrl(url, headers, prefix = prefix).let { videoList.addAll(it) }
}
if (embedUrl.contains("wishembed") || embedUrl.contains("streamwish") || embedUrl.contains("strwish") || embedUrl.contains("wish")) {
val docHeaders = headers.newBuilder()
.add("Origin", "https://streamwish.to")
.add("Referer", "https://streamwish.to/")
.build()
StreamWishExtractor(client, docHeaders).videosFromUrl(url, videoNameGen = { "$prefix StreamWish:$it" }).also(videoList::addAll)
}
if (embedUrl.contains("doodstream") || embedUrl.contains("dood.")) {
val url2 = url.replace("https://doodstream.com/e/", "https://dood.to/e/")
DoodExtractor(client).videoFromUrl(url2, "$prefix DoodStream", false)?.let { videoList.add(it) }
}
if (embedUrl.contains("streamlare")) {
StreamlareExtractor(client).videosFromUrl(url, prefix = prefix).let { videoList.addAll(it) }
}
if (embedUrl.contains("yourupload") || embedUrl.contains("upload")) {
YourUploadExtractor(client).videoFromUrl(url, headers = headers, prefix = prefix).let { videoList.addAll(it) }
}
if (embedUrl.contains("burstcloud") || embedUrl.contains("burst")) {
BurstCloudExtractor(client).videoFromUrl(url, headers = headers, prefix = prefix).let { videoList.addAll(it) }
}
if (embedUrl.contains("fastream")) {
FastreamExtractor(client).videoFromUrl(url, prefix = "$prefix Fastream:").forEach { videoList.add(it) }
}
if (embedUrl.contains("upstream")) {
UpstreamExtractor(client).videosFromUrl(url, prefix = prefix).let { videoList.addAll(it) }
}
if (embedUrl.contains("streamtape") || embedUrl.contains("stp") || embedUrl.contains("stape")) {
StreamTapeExtractor(client).videoFromUrl(url, quality = "$prefix StreamTape")?.let { videoList.add(it) }
}
if (embedUrl.contains("tomatomatela")) {
runCatching {
val mainUrl = url.substringBefore("/embed.html#").substringAfter("https://") val mainUrl = url.substringBefore("/embed.html#").substringAfter("https://")
val headers = headers.newBuilder() val headers = headers.newBuilder()
.set("authority", mainUrl) .set("authority", mainUrl)
@ -148,67 +249,21 @@ class CuevanaCh(override val name: String, override val baseUrl: String) : Confi
val status = json["status"]!!.jsonPrimitive!!.content val status = json["status"]!!.jsonPrimitive!!.content
val file = json["file"]!!.jsonPrimitive!!.content val file = json["file"]!!.jsonPrimitive!!.content
if (status == "200") { videoList.add(Video(file, "$prefix Tomatomatela", file, headers = null)) } if (status == "200") { videoList.add(Video(file, "$prefix Tomatomatela", file, headers = null)) }
}
}
if (embedUrl.contains("filelions") || embedUrl.contains("lion")) {
StreamWishExtractor(client, headers).videosFromUrl(url, videoNameGen = { "$prefix FileLions:$it" }).also(videoList::addAll)
}
} catch (_: Exception) { } } catch (_: Exception) { }
}
if (embedUrl.contains("yourupload")) {
val videos = YourUploadExtractor(client).videoFromUrl(url, headers = headers)
videoList.addAll(videos)
}
if (embedUrl.contains("doodstream") || embedUrl.contains("dood.")) {
DoodExtractor(client).videoFromUrl(url, "$prefix DoodStream", false)
?.let { videoList.add(it) }
}
if (embedUrl.contains("okru")) {
videoList.addAll(
OkruExtractor(client).videosFromUrl(url, prefix, true),
)
}
if (embedUrl.contains("voe")) {
VoeExtractor(client).videoFromUrl(url, "$prefix Voe")?.let { videoList.add(it) }
}
if (embedUrl.contains("streamtape")) {
StreamTapeExtractor(client).videoFromUrl(url, "$prefix StreamTape")?.let { videoList.add(it) }
}
if (embedUrl.contains("wishembed") || embedUrl.contains("streamwish") || embedUrl.contains("wish")) {
StreamWishExtractor(client, headers).videosFromUrl(url) { "$prefix StreamWish:$it" }
.also(videoList::addAll)
}
return videoList return videoList
} }
private fun urlServerSolver(url: String): String = if (url.startsWith("https")) url else if (url.startsWith("//")) "https:$url" else "$baseUrl/$url"
private fun fetchUrls(text: String?): List<String> {
if (text.isNullOrEmpty()) return listOf()
val linkRegex = Regex("""(https?://(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&/=]*))""")
return linkRegex.findAll(text).map { it.value.trim().removeSurrounding("\"") }.toList()
}
override fun videoListSelector() = throw Exception("not used") override fun videoListSelector() = throw Exception("not used")
override fun videoUrlParse(document: Document) = throw Exception("not used") override fun videoUrlParse(document: Document) = throw Exception("not used")
override fun videoFromElement(element: Element) = throw Exception("not used") override fun videoFromElement(element: Element) = throw Exception("not used")
override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString("preferred_quality", "Voex")
if (quality != null) {
val newList = mutableListOf<Video>()
var preferred = 0
for (video in this) {
if (video.quality == quality) {
newList.add(preferred, video)
preferred++
} else {
newList.add(video)
}
}
return newList
}
return this
}
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val filterList = if (filters.isEmpty()) getFilterList() else filters val filterList = if (filters.isEmpty()) getFilterList() else filters
val genreFilter = filterList.find { it is GenreFilter } as GenreFilter val genreFilter = filterList.find { it is GenreFilter } as GenreFilter
@ -275,22 +330,32 @@ class CuevanaCh(override val name: String, override val baseUrl: String) : Confi
), ),
) )
override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
val lang = preferences.getString(PREF_LANGUAGE_KEY, PREF_LANGUAGE_DEFAULT)!!
return this.sortedWith(
compareBy(
{ it.quality.contains(lang) },
{ it.quality.contains(server, true) },
{ it.quality.contains(quality) },
{ Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 },
),
).reversed()
}
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
AnimeFilter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { AnimeFilter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second fun toUriPart() = vals[state].second
} }
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
val qualities = arrayOf( ListPreference(screen.context).apply {
"Streamlare:1080p", "Streamlare:720p", "Streamlare:480p", "Streamlare:360p", "Streamlare:240p", // Streamlare key = PREF_LANGUAGE_KEY
"StreamTape", "Amazon", "Voex", "DoodStream", "YourUpload", title = "Preferred language"
) entries = LANGUAGE_LIST
val videoQualityPref = ListPreference(screen.context).apply { entryValues = LANGUAGE_LIST
key = "preferred_quality" setDefaultValue(PREF_LANGUAGE_DEFAULT)
title = "Preferred quality"
entries = qualities
entryValues = qualities
setDefaultValue("Voex")
summary = "%s" summary = "%s"
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
@ -299,7 +364,38 @@ class CuevanaCh(override val name: String, override val baseUrl: String) : Confi
val entry = entryValues[index] as String val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit() preferences.edit().putString(key, entry).commit()
} }
}.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY
title = "Preferred quality"
entries = QUALITY_LIST
entryValues = QUALITY_LIST
setDefaultValue(PREF_QUALITY_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
} }
screen.addPreference(videoQualityPref) }.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_SERVER_KEY
title = "Preferred server"
entries = SERVER_LIST
entryValues = SERVER_LIST
setDefaultValue(PREF_SERVER_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}.also(screen::addPreference)
} }
} }

View File

@ -46,9 +46,7 @@ class CuevanaEu(override val name: String, override val baseUrl: String) : Confi
override val supportsLatest = false override val supportsLatest = false
private val json = Json { private val json = Json { ignoreUnknownKeys = true }
ignoreUnknownKeys = true
}
override val client: OkHttpClient = network.cloudflareClient override val client: OkHttpClient = network.cloudflareClient
@ -56,6 +54,24 @@ class CuevanaEu(override val name: String, override val baseUrl: String) : Confi
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
} }
companion object {
private const val PREF_LANGUAGE_KEY = "preferred_language"
private const val PREF_LANGUAGE_DEFAULT = "[LAT]"
private val LANGUAGE_LIST = arrayOf("[LAT]", "[ENG]", "[CAST]", "[JAP]")
private const val PREF_QUALITY_KEY = "preferred_quality"
private const val PREF_QUALITY_DEFAULT = "1080"
private val QUALITY_LIST = arrayOf("1080", "720", "480", "360")
private const val PREF_SERVER_KEY = "preferred_server"
private const val PREF_SERVER_DEFAULT = "Voe"
private val SERVER_LIST = arrayOf(
"Tomatomatela", "YourUpload", "Doodstream", "Okru",
"Voe", "StreamTape", "StreamWish", "Filemoon",
"FileLions",
)
}
override fun popularAnimeSelector(): String = ".MovieList .TPostMv .TPost" override fun popularAnimeSelector(): String = ".MovieList .TPostMv .TPost"
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/peliculas/estrenos/page/$page") override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/peliculas/estrenos/page/$page")
@ -72,7 +88,7 @@ class CuevanaEu(override val name: String, override val baseUrl: String) : Confi
responseJson.props?.pageProps?.movies?.map { animeItem -> responseJson.props?.pageProps?.movies?.map { animeItem ->
val anime = SAnime.create() val anime = SAnime.create()
val preSlug = animeItem.url?.slug ?: "" val preSlug = animeItem.url?.slug ?: ""
val type = if (preSlug.startsWith("series")) "serie" else "pelicula" val type = if (preSlug.startsWith("series")) "ver-serie" else "ver-pelicula"
anime.title = animeItem.titles?.name ?: "" anime.title = animeItem.titles?.name ?: ""
anime.thumbnail_url = animeItem.images?.poster?.replace("/original/", "/w200/") ?: "" anime.thumbnail_url = animeItem.images?.poster?.replace("/original/", "/w200/") ?: ""
@ -89,7 +105,7 @@ class CuevanaEu(override val name: String, override val baseUrl: String) : Confi
override fun episodeListParse(response: Response): List<SEpisode> { override fun episodeListParse(response: Response): List<SEpisode> {
val episodes = mutableListOf<SEpisode>() val episodes = mutableListOf<SEpisode>()
val document = response.asJsoup() val document = response.asJsoup()
if (response.request.url.toString().contains("/serie/")) { if (response.request.url.toString().contains("/ver-serie/")) {
val script = document.selectFirst("script:containsData({\"props\":{\"pageProps\":{)")!!.data() val script = document.selectFirst("script:containsData({\"props\":{\"pageProps\":{)")!!.data()
val responseJson = json.decodeFromString<AnimeEpisodesList>(script) val responseJson = json.decodeFromString<AnimeEpisodesList>(script)
responseJson.props?.pageProps?.thisSerie?.seasons?.map { responseJson.props?.pageProps?.thisSerie?.seasons?.map {
@ -215,7 +231,7 @@ class CuevanaEu(override val name: String, override val baseUrl: String) : Confi
OkruExtractor(client).videosFromUrl(url, prefix, true).also(videoList::addAll) OkruExtractor(client).videosFromUrl(url, prefix, true).also(videoList::addAll)
} }
if (embedUrl.contains("voe")) { if (embedUrl.contains("voe")) {
VoeExtractor(client).videoFromUrl(url, "$prefix Voex")?.let { videoList.add(it) } VoeExtractor(client).videoFromUrl(url, prefix = "$prefix Voe:")?.let { videoList.add(it) }
} }
if (embedUrl.contains("streamtape")) { if (embedUrl.contains("streamtape")) {
StreamTapeExtractor(client).videoFromUrl(url, "$prefix StreamTape")?.let { videoList.add(it) } StreamTapeExtractor(client).videoFromUrl(url, "$prefix StreamTape")?.let { videoList.add(it) }
@ -227,6 +243,9 @@ class CuevanaEu(override val name: String, override val baseUrl: String) : Confi
if (embedUrl.contains("filemoon") || embedUrl.contains("moonplayer")) { if (embedUrl.contains("filemoon") || embedUrl.contains("moonplayer")) {
FilemoonExtractor(client).videosFromUrl(url, "$prefix Filemoon:").also(videoList::addAll) FilemoonExtractor(client).videosFromUrl(url, "$prefix Filemoon:").also(videoList::addAll)
} }
if (embedUrl.contains("filelions") || embedUrl.contains("lion")) {
StreamWishExtractor(client, headers).videosFromUrl(url, videoNameGen = { "$prefix FileLions:$it" }).also(videoList::addAll)
}
return videoList return videoList
} }
@ -237,21 +256,17 @@ class CuevanaEu(override val name: String, override val baseUrl: String) : Confi
override fun videoFromElement(element: Element) = throw Exception("not used") override fun videoFromElement(element: Element) = throw Exception("not used")
override fun List<Video>.sort(): List<Video> { override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString("preferred_quality", "Voex") val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
if (quality != null) { val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
val newList = mutableListOf<Video>() val lang = preferences.getString(PREF_LANGUAGE_KEY, PREF_LANGUAGE_DEFAULT)!!
var preferred = 0 return this.sortedWith(
for (video in this) { compareBy(
if (video.quality == quality) { { it.quality.contains(lang) },
newList.add(preferred, video) { it.quality.contains(server, true) },
preferred++ { it.quality.contains(quality) },
} else { { Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 },
newList.add(video) ),
} ).reversed()
}
return newList
}
return this
} }
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
@ -280,13 +295,14 @@ class CuevanaEu(override val name: String, override val baseUrl: String) : Confi
val newAnime = SAnime.create() val newAnime = SAnime.create()
val script = document.selectFirst("script:containsData({\"props\":{\"pageProps\":{)")!!.data() val script = document.selectFirst("script:containsData({\"props\":{\"pageProps\":{)")!!.data()
val responseJson = json.decodeFromString<AnimeEpisodesList>(script) val responseJson = json.decodeFromString<AnimeEpisodesList>(script)
if (response.request.url.toString().contains("/serie/")) { if (response.request.url.toString().contains("/ver-serie/")) {
val data = responseJson.props?.pageProps?.thisSerie val data = responseJson.props?.pageProps?.thisSerie
newAnime.status = SAnime.UNKNOWN newAnime.status = SAnime.UNKNOWN
newAnime.title = data?.titles?.name ?: "" newAnime.title = data?.titles?.name ?: ""
newAnime.description = data?.overview ?: "" newAnime.description = data?.overview ?: ""
newAnime.thumbnail_url = data?.images?.poster?.replace("/original/", "/w500/") newAnime.thumbnail_url = data?.images?.poster?.replace("/original/", "/w500/")
newAnime.genre = data?.genres?.joinToString { it.name ?: "" } newAnime.genre = data?.genres?.joinToString { it.name ?: "" }
newAnime.artist = data?.cast?.acting?.firstOrNull()?.name ?: ""
newAnime.setUrlWithoutDomain(response.request.url.toString()) newAnime.setUrlWithoutDomain(response.request.url.toString())
} else { } else {
val data = responseJson.props?.pageProps?.thisMovie val data = responseJson.props?.pageProps?.thisMovie
@ -295,6 +311,7 @@ class CuevanaEu(override val name: String, override val baseUrl: String) : Confi
newAnime.description = data?.overview ?: "" newAnime.description = data?.overview ?: ""
newAnime.thumbnail_url = data?.images?.poster?.replace("/original/", "/w500/") newAnime.thumbnail_url = data?.images?.poster?.replace("/original/", "/w500/")
newAnime.genre = data?.genres?.joinToString { it.name ?: "" } newAnime.genre = data?.genres?.joinToString { it.name ?: "" }
newAnime.artist = data?.cast?.acting?.firstOrNull()?.name ?: ""
newAnime.setUrlWithoutDomain(response.request.url.toString()) newAnime.setUrlWithoutDomain(response.request.url.toString())
} }
@ -342,17 +359,12 @@ class CuevanaEu(override val name: String, override val baseUrl: String) : Confi
} }
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
val qualities = arrayOf( ListPreference(screen.context).apply {
"Streamlare:1080p", "Streamlare:720p", "Streamlare:480p", "Streamlare:360p", "Streamlare:240p", // Streamlare} key = PREF_LANGUAGE_KEY
"Okru:1080p", "Okru:720p", "Okru:480p", "Okru:360p", "Okru:240p", // Okru title = "Preferred language"
"StreamTape", "Amazon", "Voex", "DoodStream", "YourUpload", "Filemoon", "StreamWish", "Tomatomatela", "YourUpload", entries = LANGUAGE_LIST
) entryValues = LANGUAGE_LIST
val videoQualityPref = ListPreference(screen.context).apply { setDefaultValue(PREF_LANGUAGE_DEFAULT)
key = "preferred_quality"
title = "Preferred quality"
entries = qualities
entryValues = qualities
setDefaultValue("Voex")
summary = "%s" summary = "%s"
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
@ -361,7 +373,38 @@ class CuevanaEu(override val name: String, override val baseUrl: String) : Confi
val entry = entryValues[index] as String val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit() preferences.edit().putString(key, entry).commit()
} }
}.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY
title = "Preferred quality"
entries = QUALITY_LIST
entryValues = QUALITY_LIST
setDefaultValue(PREF_QUALITY_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
} }
screen.addPreference(videoQualityPref) }.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_SERVER_KEY
title = "Preferred server"
entries = SERVER_LIST
entryValues = SERVER_LIST
setDefaultValue(PREF_SERVER_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}.also(screen::addPreference)
} }
} }

View File

@ -5,7 +5,7 @@ import eu.kanade.tachiyomi.animesource.AnimeSourceFactory
class CuevanaFactory : AnimeSourceFactory { class CuevanaFactory : AnimeSourceFactory {
override fun createSources(): List<AnimeSource> = listOf( override fun createSources(): List<AnimeSource> = listOf(
CuevanaCh("Cuevana3Ch", "https://www12.cuevana3.ch"), CuevanaCh("Cuevana3Ch", "https://ww1.cuevana3.ch"),
CuevanaEu("Cuevana3Eu", "https://www.cuevana-3.eu"), CuevanaEu("Cuevana3Eu", "https://www.cuevana3.eu"),
) )
} }

View File

@ -5,17 +5,27 @@ ext {
extName = 'Doramasflix' extName = 'Doramasflix'
pkgNameSuffix = 'es.doramasflix' pkgNameSuffix = 'es.doramasflix'
extClass = '.Doramasflix' extClass = '.Doramasflix'
extVersionCode = 10 extVersionCode = 11
libVersion = '13' libVersion = '13'
} }
dependencies { dependencies {
implementation(project(':lib-vudeo-extractor'))
implementation(project(':lib-uqload-extractor')) implementation(project(':lib-uqload-extractor'))
implementation(project(':lib-mixdrop-extractor')) implementation(project(':lib-streamwish-extractor'))
implementation(project(':lib-filemoon-extractor'))
implementation(project(':lib-streamlare-extractor'))
implementation(project(':lib-yourupload-extractor'))
implementation(project(':lib-streamtape-extractor')) implementation(project(':lib-streamtape-extractor'))
implementation(project(':lib-okru-extractor'))
implementation(project(':lib-dood-extractor')) implementation(project(':lib-dood-extractor'))
implementation(project(':lib-voe-extractor')) implementation(project(':lib-voe-extractor'))
implementation(project(':lib-okru-extractor'))
implementation(project(':lib-mp4upload-extractor'))
implementation(project(':lib-mixdrop-extractor'))
implementation(project(':lib-burstcloud-extractor'))
implementation(project(':lib-fastream-extractor'))
implementation(project(':lib-upstream-extractor'))
implementation(project(':lib-streamhidevid-extractor'))
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View File

@ -11,11 +11,20 @@ import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
import eu.kanade.tachiyomi.lib.mixdropextractor.MixDropExtractor import eu.kanade.tachiyomi.lib.burstcloudextractor.BurstCloudExtractor
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
import eu.kanade.tachiyomi.lib.fastreamextractor.FastreamExtractor
import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
import eu.kanade.tachiyomi.lib.streamhidevidextractor.StreamHideVidExtractor
import eu.kanade.tachiyomi.lib.streamlareextractor.StreamlareExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
import eu.kanade.tachiyomi.lib.upstreamextractor.UpstreamExtractor
import eu.kanade.tachiyomi.lib.uqloadextractor.UqloadExtractor import eu.kanade.tachiyomi.lib.uqloadextractor.UqloadExtractor
import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
@ -26,6 +35,7 @@ import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Headers import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
@ -55,6 +65,29 @@ class Doramasflix : ConfigurableAnimeSource, AnimeHttpSource() {
private val json: Json by injectLazy() private val json: Json by injectLazy()
companion object {
private const val PREF_LANGUAGE_KEY = "preferred_language"
private const val PREF_LANGUAGE_DEFAULT = "[LAT]"
private val LANGUAGE_LIST = arrayOf(
"[ENG]", "[CAST]", "[LAT]", "[SUB]", "[POR]",
"[COR]", "[JAP]", "[MAN]", "[TAI]", "[FIL]",
"[IND]", "[VIET]",
)
private const val PREF_QUALITY_KEY = "preferred_quality"
private const val PREF_QUALITY_DEFAULT = "1080"
private val QUALITY_LIST = arrayOf("1080", "720", "480", "360")
private const val PREF_SERVER_KEY = "preferred_server"
private const val PREF_SERVER_DEFAULT = "Voe"
private val SERVER_LIST = arrayOf(
"YourUpload", "BurstCloud", "Voe", "Mp4Upload", "Doodstream",
"Upload", "BurstCloud", "Upstream", "StreamTape", "Amazon",
"Fastream", "Filemoon", "StreamWish", "Okru", "Streamlare",
"Uqload",
)
}
private val preferences: SharedPreferences by lazy { private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
} }
@ -89,21 +122,21 @@ class Doramasflix : ConfigurableAnimeSource, AnimeHttpSource() {
if (el.data().contains("{\"props\":{\"pageProps\":{")) { if (el.data().contains("{\"props\":{\"pageProps\":{")) {
val apolloState = json.decodeFromString<JsonObject>(el.data())!!.jsonObject["props"]!!.jsonObject["pageProps"]!!.jsonObject["apolloState"]!!.jsonObject val apolloState = json.decodeFromString<JsonObject>(el.data())!!.jsonObject["props"]!!.jsonObject["pageProps"]!!.jsonObject["apolloState"]!!.jsonObject
val dorama = apolloState!!.entries!!.firstOrNull()!!.value!!.jsonObject val dorama = apolloState!!.entries!!.firstOrNull()!!.value!!.jsonObject
val genres = try { apolloState.entries.filter { x -> x.key.contains("genres") }.joinToString { it.value.jsonObject["name"]!!.jsonPrimitive.content } } catch (_: Exception) { null } val genres = try { apolloState.entries.filter { x -> x.key.contains("genres") }.joinToString { it.value.jsonObject["name"]!!.jsonPrimitive.content } } catch (_: Exception) { "" }
val network = try { apolloState.entries.firstOrNull { x -> x.key.contains("networks") }?.value?.jsonObject?.get("name")!!.jsonPrimitive.content } catch (_: Exception) { null } val network = try { apolloState.entries.firstOrNull { x -> x.key.contains("networks") }?.value?.jsonObject?.get("name")!!.jsonPrimitive.content } catch (_: Exception) { "" }
val artist = try { dorama["cast"]?.jsonObject?.get("json")?.jsonArray?.firstOrNull()?.jsonObject?.get("name")?.jsonPrimitive?.content } catch (_: Exception) { null } val artist = try { dorama["cast"]?.jsonObject?.get("json")?.jsonArray?.firstOrNull()?.jsonObject?.get("name")?.jsonPrimitive?.content } catch (_: Exception) { "" }
val type = dorama["__typename"]!!.jsonPrimitive.content.lowercase() val type = try { dorama["__typename"]!!.jsonPrimitive.content.lowercase() } catch (_: Exception) { "" }
val poster = dorama["poster_path"]!!.jsonPrimitive.content val poster = try { dorama["poster_path"]!!.jsonPrimitive.content } catch (_: Exception) { "" }
val urlImg = poster.ifEmpty { dorama["poster"]!!.jsonPrimitive.content } val urlImg = try { poster.ifEmpty { dorama["poster"]!!.jsonPrimitive.content } } catch (_: Exception) { "" }
val id = dorama["_id"]!!.jsonPrimitive!!.content val id = dorama["_id"]!!.jsonPrimitive.content
anime.title = "${dorama["name"]!!.jsonPrimitive!!.content} (${dorama["name_es"]!!.jsonPrimitive!!.content})" anime.title = "${dorama["name"]?.jsonPrimitive?.content} (${dorama["name_es"]?.jsonPrimitive?.content})"
anime.description = dorama["overview"]!!.jsonPrimitive!!.content.trim() anime.description = dorama["overview"]?.jsonPrimitive?.content?.trim() ?: ""
anime.genre = genres if (genres.isNotEmpty()) anime.genre = genres
anime.author = network if (network.isNotEmpty()) anime.author = network
anime.artist = artist if (artist != null) anime.artist = artist
anime.status = if (type == "movie") SAnime.COMPLETED else SAnime.UNKNOWN if (type.isNotEmpty()) anime.status = if (type == "movie") SAnime.COMPLETED else SAnime.UNKNOWN
anime.thumbnail_url = externalOrInternalImg(urlImg) if (urlImg.isNotEmpty()) anime.thumbnail_url = externalOrInternalImg(urlImg)
anime.setUrlWithoutDomain(urlSolverByType(dorama["__typename"]!!.jsonPrimitive!!.content, dorama["slug"]!!.jsonPrimitive!!.content, id)) anime.setUrlWithoutDomain(urlSolverByType(dorama["__typename"]!!.jsonPrimitive!!.content, dorama["slug"]!!.jsonPrimitive!!.content, id))
} }
} }
@ -196,6 +229,25 @@ class Doramasflix : ConfigurableAnimeSource, AnimeHttpSource() {
return parsePopularAnimeJson(responseString) return parsePopularAnimeJson(responseString)
} }
private val languages = arrayOf(
Pair("36", "[ENG]"),
Pair("37", "[CAST]"),
Pair("38", "[LAT]"),
Pair("192", "[SUB]"),
Pair("1327", "[POR]"),
Pair("13109", "[COR]"),
Pair("13110", "[JAP]"),
Pair("13111", "[MAN]"),
Pair("13112", "[TAI]"),
Pair("13113", "[FIL]"),
Pair("13114", "[IND]"),
Pair("343422", "[VIET]"),
)
private fun String.getLang(): String {
return languages.firstOrNull { it.first == this }?.second ?: ""
}
private fun parsePopularAnimeJson(jsonLine: String?): AnimesPage { private fun parsePopularAnimeJson(jsonLine: String?): AnimesPage {
val jsonData = jsonLine ?: return AnimesPage(emptyList(), false) val jsonData = jsonLine ?: return AnimesPage(emptyList(), false)
val animeList = mutableListOf<SAnime>() val animeList = mutableListOf<SAnime>()
@ -212,7 +264,6 @@ class Doramasflix : ConfigurableAnimeSource, AnimeHttpSource() {
anime.description = it.jsonObject!!["overview"]!!.jsonPrimitive!!.content anime.description = it.jsonObject!!["overview"]!!.jsonPrimitive!!.content
anime.genre = genres anime.genre = genres
anime.thumbnail_url = externalOrInternalImg(urlImg, true) anime.thumbnail_url = externalOrInternalImg(urlImg, true)
// "https://image.tmdb.org/t/p/w220_and_h330_face${it.jsonObject!!["poster_path"]!!.jsonPrimitive!!.content}"
anime.setUrlWithoutDomain(urlSolverByType(it.jsonObject!!["__typename"]!!.jsonPrimitive!!.content, it.jsonObject!!["slug"]!!.jsonPrimitive!!.content, id)) anime.setUrlWithoutDomain(urlSolverByType(it.jsonObject!!["__typename"]!!.jsonPrimitive!!.content, it.jsonObject!!["slug"]!!.jsonPrimitive!!.content, id))
animeList.add(anime) animeList.add(anime)
} }
@ -299,52 +350,146 @@ class Doramasflix : ConfigurableAnimeSource, AnimeHttpSource() {
return POST(apiUrl, popularRequestHeaders, body) return POST(apiUrl, popularRequestHeaders, body)
} }
private fun fetchUrls(text: String?): List<String> {
if (text.isNullOrEmpty()) return listOf()
val linkRegex = "(https?://(www\\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_+.~#?&/=]*))".toRegex()
return linkRegex.findAll(text).map { it.value.trim().removeSurrounding("\"") }.toList()
}
override fun videoListParse(response: Response): List<Video> { override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup() val document = response.asJsoup()
val videos = mutableListOf<Video>() val videoList = mutableListOf<Video>()
val script = document.selectFirst("script:containsData({\"props\":{\"pageProps\":{)")!!.data() val jsonData = document.selectFirst("script:containsData({\"props\":{\"pageProps\":{)")!!.data()
fetchUrls(script).map { link -> val apolloState = json.decodeFromString<JsonObject>(jsonData).jsonObject["props"]!!.jsonObject["pageProps"]!!.jsonObject["apolloState"]!!.jsonObject
resolveVideoServer(link).let { videos.addAll(it) } val episode = apolloState.entries.firstOrNull { x -> x.key.contains("Episode:") }!!.value.jsonObject
val linksOnline = episode["links_online"]!!.jsonObject["json"]!!.jsonArray
linksOnline.map {
val link = it.jsonObject["link"]!!.jsonPrimitive.content
val lang = it.jsonObject["lang"]?.jsonPrimitive?.content?.getLang() ?: ""
serverVideoResolver(link, lang).also(videoList::addAll)
} }
return videos return videoList
} }
private fun resolveVideoServer(link: String): List<Video> { private fun serverVideoResolver(url: String, prefix: String = ""): List<Video> {
return runCatching { val videoList = mutableListOf<Video>()
when { val embedUrl = url.lowercase()
"streamtape" in link -> try {
StreamTapeExtractor(client).videoFromUrl(link)?.let(::listOf) if (embedUrl.contains("voe")) {
"mixdrop" in link -> VoeExtractor(client).videoFromUrl(url, prefix = "$prefix Voe:")?.let { videoList.add(it) }
MixDropExtractor(client).videoFromUrl(link)
"uqload.co" in link ->
UqloadExtractor(client).videosFromUrl(link)
"ok.ru" in link ->
OkruExtractor(client).videosFromUrl(link)
"voe" in link ->
VoeExtractor(client).videoFromUrl(link, "Voex")?.let(::listOf)
else -> null
} }
}.getOrNull() ?: emptyList() if ((embedUrl.contains("amazon") || embedUrl.contains("amz")) && !embedUrl.contains("disable")) {
val body = client.newCall(GET(url)).execute().asJsoup()
if (body.select("script:containsData(var shareId)").toString().isNotBlank()) {
val shareId = body.selectFirst("script:containsData(var shareId)")!!.data()
.substringAfter("shareId = \"").substringBefore("\"")
val amazonApiJson = client.newCall(GET("https://www.amazon.com/drive/v1/shares/$shareId?resourceVersion=V2&ContentType=JSON&asset=ALL"))
.execute().asJsoup()
val epId = amazonApiJson.toString().substringAfter("\"id\":\"").substringBefore("\"")
val amazonApi =
client.newCall(GET("https://www.amazon.com/drive/v1/nodes/$epId/children?resourceVersion=V2&ContentType=JSON&limit=200&sort=%5B%22kind+DESC%22%2C+%22modifiedDate+DESC%22%5D&asset=ALL&tempLink=true&shareId=$shareId"))
.execute().asJsoup()
val videoUrl = amazonApi.toString().substringAfter("\"FOLDER\":").substringAfter("tempLink\":\"").substringBefore("\"")
videoList.add(Video(videoUrl, "$prefix Amazon", videoUrl))
}
}
if (embedUrl.contains("ok.ru") || embedUrl.contains("okru")) {
OkruExtractor(client).videosFromUrl(url, prefix = "$prefix ").also(videoList::addAll)
}
if (embedUrl.contains("filemoon") || embedUrl.contains("moonplayer")) {
val vidHeaders = headers.newBuilder()
.add("Origin", "https://${url.toHttpUrl().host}")
.add("Referer", "https://${url.toHttpUrl().host}/")
.build()
FilemoonExtractor(client).videosFromUrl(url, prefix = "$prefix Filemoon:", headers = vidHeaders).also(videoList::addAll)
}
if (embedUrl.contains("uqload")) {
UqloadExtractor(client).videosFromUrl(url, prefix = prefix).also(videoList::addAll)
}
if (embedUrl.contains("mp4upload")) {
Mp4uploadExtractor(client).videosFromUrl(url, prefix = "$prefix ", headers = headers).let { videoList.addAll(it) }
}
if (embedUrl.contains("wishembed") || embedUrl.contains("streamwish") || embedUrl.contains("strwish") || embedUrl.contains("wish")) {
val docHeaders = headers.newBuilder()
.add("Origin", "https://streamwish.to")
.add("Referer", "https://streamwish.to/")
.build()
StreamWishExtractor(client, docHeaders).videosFromUrl(url, videoNameGen = { "$prefix StreamWish:$it" }).also(videoList::addAll)
}
if (embedUrl.contains("doodstream") || embedUrl.contains("dood.")) {
val url2 = url.replace("https://doodstream.com/e/", "https://dood.to/e/")
DoodExtractor(client).videoFromUrl(url2, "$prefix DoodStream", false)?.let { videoList.add(it) }
}
if (embedUrl.contains("streamlare")) {
StreamlareExtractor(client).videosFromUrl(url, prefix = prefix).let { videoList.addAll(it) }
}
if (embedUrl.contains("yourupload") || embedUrl.contains("upload")) {
YourUploadExtractor(client).videoFromUrl(url, headers = headers, prefix = "$prefix ").let { videoList.addAll(it) }
}
if (embedUrl.contains("burstcloud") || embedUrl.contains("burst")) {
BurstCloudExtractor(client).videoFromUrl(url, headers = headers, prefix = "$prefix ").let { videoList.addAll(it) }
}
if (embedUrl.contains("fastream")) {
FastreamExtractor(client).videoFromUrl(url, prefix = "$prefix Fastream:").forEach { videoList.add(it) }
}
if (embedUrl.contains("upstream")) {
UpstreamExtractor(client).videosFromUrl(url, prefix = "$prefix ").let { videoList.addAll(it) }
}
if (embedUrl.contains("streamtape") || embedUrl.contains("stp") || embedUrl.contains("stape")) {
StreamTapeExtractor(client).videoFromUrl(url, quality = "$prefix StreamTape")?.let { videoList.add(it) }
}
if (embedUrl.contains("ahvsh") || embedUrl.contains("streamhide")) {
StreamHideVidExtractor(client).videosFromUrl(url, "$prefix ").let { videoList.addAll(it) }
}
if (embedUrl.contains("filelions") || embedUrl.contains("lion")) {
StreamWishExtractor(client, headers).videosFromUrl(url, videoNameGen = { "$prefix FileLions:$it" }).also(videoList::addAll)
}
if (embedUrl.contains("tomatomatela")) {
runCatching {
val mainUrl = url.substringBefore("/embed.html#").substringAfter("https://")
val headers = headers.newBuilder()
.set("authority", mainUrl)
.set("accept", "application/json, text/javascript, */*; q=0.01")
.set("accept-language", "es-MX,es-419;q=0.9,es;q=0.8,en;q=0.7")
.set("sec-ch-ua", "\"Chromium\";v=\"106\", \"Google Chrome\";v=\"106\", \"Not;A=Brand\";v=\"99\"")
.set("sec-ch-ua-mobile", "?0")
.set("sec-ch-ua-platform", "Windows")
.set("sec-fetch-dest", "empty")
.set("sec-fetch-mode", "cors")
.set("sec-fetch-site", "same-origin")
.set("x-requested-with", "XMLHttpRequest")
.build()
val token = url.substringAfter("/embed.html#")
val urlRequest = "https://$mainUrl/details.php?v=$token"
val response = client.newCall(GET(urlRequest, headers = headers)).execute().asJsoup()
val bodyText = response.select("body").text()
val json = json.decodeFromString<JsonObject>(bodyText)
val status = json["status"]!!.jsonPrimitive!!.content
val file = json["file"]!!.jsonPrimitive!!.content
if (status == "200") { videoList.add(Video(file, "$prefix Tomatomatela", file, headers = null)) }
}
}
} catch (_: Exception) { }
return videoList
}
override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
val lang = preferences.getString(PREF_LANGUAGE_KEY, PREF_LANGUAGE_DEFAULT)!!
return this.sortedWith(
compareBy(
{ it.quality.contains(lang) },
{ it.quality.contains(server, true) },
{ it.quality.contains(quality) },
{ Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 },
),
).reversed()
} }
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
val qualities = arrayOf( ListPreference(screen.context).apply {
"Okru:1080p", "Okru:720p", "Okru:480p", "Okru:360p", "Okru:240p", "Okru:144p", // Okru key = PREF_LANGUAGE_KEY
"Streamlare:1080p", "Streamlare:720p", "Streamlare:480p", "Streamlare:360p", "Streamlare:240p", // Streamlare title = "Preferred language"
"StreamTape", "Voex", "Uqload", "MixDrop", entries = LANGUAGE_LIST
) entryValues = LANGUAGE_LIST
val videoQualityPref = ListPreference(screen.context).apply { setDefaultValue(PREF_LANGUAGE_DEFAULT)
key = "preferred_quality"
title = "Preferred quality"
entries = qualities
entryValues = qualities
setDefaultValue("Voex")
summary = "%s" summary = "%s"
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
@ -353,7 +498,38 @@ class Doramasflix : ConfigurableAnimeSource, AnimeHttpSource() {
val entry = entryValues[index] as String val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit() preferences.edit().putString(key, entry).commit()
} }
}.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY
title = "Preferred quality"
entries = QUALITY_LIST
entryValues = QUALITY_LIST
setDefaultValue(PREF_QUALITY_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
} }
screen.addPreference(videoQualityPref) }.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_SERVER_KEY
title = "Preferred server"
entries = SERVER_LIST
entryValues = SERVER_LIST
setDefaultValue(PREF_SERVER_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}.also(screen::addPreference)
} }
} }

View File

@ -5,7 +5,7 @@ ext {
extName = 'Gnula' extName = 'Gnula'
pkgNameSuffix = 'es.gnula' pkgNameSuffix = 'es.gnula'
extClass = '.Gnula' extClass = '.Gnula'
extVersionCode = 8 extVersionCode = 9
libVersion = '13' libVersion = '13'
} }
@ -15,6 +15,15 @@ dependencies {
implementation project(path: ':lib-yourupload-extractor') implementation project(path: ':lib-yourupload-extractor')
implementation project(path: ':lib-voe-extractor') implementation project(path: ':lib-voe-extractor')
implementation project(path: ':lib-dood-extractor') implementation project(path: ':lib-dood-extractor')
implementation(project(':lib-uqload-extractor'))
implementation(project(':lib-mp4upload-extractor'))
implementation(project(':lib-streamlare-extractor'))
implementation(project(':lib-fastream-extractor'))
implementation(project(':lib-upstream-extractor'))
implementation(project(':lib-burstcloud-extractor'))
implementation(project(':lib-streamhidevid-extractor'))
implementation(project(':lib-filemoon-extractor'))
implementation(project(':lib-streamwish-extractor'))
} }

View File

@ -13,19 +13,28 @@ import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
import eu.kanade.tachiyomi.lib.burstcloudextractor.BurstCloudExtractor
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
import eu.kanade.tachiyomi.lib.fastreamextractor.FastreamExtractor
import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
import eu.kanade.tachiyomi.lib.streamhidevidextractor.StreamHideVidExtractor
import eu.kanade.tachiyomi.lib.streamlareextractor.StreamlareExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
import eu.kanade.tachiyomi.lib.upstreamextractor.UpstreamExtractor
import eu.kanade.tachiyomi.lib.uqloadextractor.UqloadExtractor
import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.jsonPrimitive
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
@ -50,12 +59,29 @@ class Gnula : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
private val json: Json by injectLazy() private val json: Json by injectLazy()
private val langValues = arrayOf("latino", "spanish", "english", "")
private val preferences: SharedPreferences by lazy { private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
} }
companion object {
const val PREF_QUALITY_KEY = "preferred_quality"
const val PREF_QUALITY_DEFAULT = "1080"
private val QUALITY_LIST = arrayOf("1080", "720", "480", "360")
private const val PREF_SERVER_KEY = "preferred_server"
private const val PREF_SERVER_DEFAULT = "Voe"
private val SERVER_LIST = arrayOf(
"YourUpload", "BurstCloud", "Voe", "Mp4Upload", "Doodstream",
"Upload", "BurstCloud", "Upstream", "StreamTape", "Amazon",
"Fastream", "Filemoon", "StreamWish", "Okru", "Streamlare",
"StreamHideVid",
)
private const val PREF_LANGUAGE_KEY = "preferred_language"
private const val PREF_LANGUAGE_DEFAULT = "[LAT]"
private val LANGUAGE_LIST = arrayOf("[LAT]", "[CAST]", "[SUB]")
private val LANGUAGE_LIST_VALUES = arrayOf("latino", "spanish", "english")
}
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/archives/movies/releases/page/$page") override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/archives/movies/releases/page/$page")
override fun popularAnimeParse(response: Response): AnimesPage { override fun popularAnimeParse(response: Response): AnimesPage {
@ -130,7 +156,6 @@ class Gnula : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun videoListParse(response: Response): List<Video> { override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup() val document = response.asJsoup()
val videoList = mutableListOf<Video>() val videoList = mutableListOf<Video>()
val langSelect = preferences.getString("preferred_lang", "latino").toString()
if (response.request.url.toString().contains("/movies/")) { if (response.request.url.toString().contains("/movies/")) {
document.select("script").forEach { script -> document.select("script").forEach { script ->
@ -139,26 +164,11 @@ class Gnula : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val props = jObject["props"]!!.jsonObject val props = jObject["props"]!!.jsonObject
val pageProps = props["pageProps"]!!.jsonObject val pageProps = props["pageProps"]!!.jsonObject
val post = pageProps["post"]!!.jsonObject val post = pageProps["post"]!!.jsonObject
var players = if (langSelect != "") { post["players"]!!.jsonObject.entries.map {
post["players"]!!.jsonObject.entries.filter { x -> x.key == langSelect && x.value.jsonArray.any() }.toList() val key = it.key
} else { val langVal = try {
post["players"]!!.jsonObject.entries.toList() LANGUAGE_LIST[LANGUAGE_LIST.indexOf(LANGUAGE_LIST_VALUES.firstOrNull { it == key })]
} } catch (_: Exception) { "" }
if (!players.any()) {
langValues.filter { x -> x != langSelect }.forEach { tmpLang ->
if (!players.any()) {
players = if (langSelect != "") {
post["players"]!!.jsonObject.entries.filter { x -> x.key == tmpLang && x.value.jsonArray.any() }.toList()
} else {
post["players"]!!.jsonObject.entries.toList()
}
}
}
}
players.map {
val lang = it.key.split(" ").joinToString(separator = " ", transform = String::capitalize)
it.value!!.jsonArray!!.map { it.value!!.jsonArray!!.map {
val server = it!!.jsonObject["result"]!!.jsonPrimitive!!.content val server = it!!.jsonObject["result"]!!.jsonPrimitive!!.content
var url = "" var url = ""
@ -167,7 +177,7 @@ class Gnula : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
url = sc.data().substringAfter("var url = '").substringBefore("';") url = sc.data().substringAfter("var url = '").substringBefore("';")
} }
} }
loadExtractor(url, lang).let { videos -> videoList.addAll(videos) } serverVideoResolver(url, langVal).let { videos -> videoList.addAll(videos) }
} }
} }
} }
@ -179,10 +189,13 @@ class Gnula : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val props = jObject["props"]!!.jsonObject val props = jObject["props"]!!.jsonObject
val pageProps = props["pageProps"]!!.jsonObject val pageProps = props["pageProps"]!!.jsonObject
val episode = pageProps["episode"]!!.jsonObject val episode = pageProps["episode"]!!.jsonObject
val players = episode["players"]!!.jsonObject.entries.filter { x -> x.key == langSelect } val players = episode["players"]!!.jsonObject.entries
.ifEmpty { episode["players"]!!.jsonObject.entries }
players.map { players.map {
val lang = it.key.split(" ").joinToString(separator = " ", transform = String::capitalize) val key = it.key
val langVal = try {
LANGUAGE_LIST[LANGUAGE_LIST.indexOf(LANGUAGE_LIST_VALUES.firstOrNull { it == key })]
} catch (_: Exception) { "" }
it.value!!.jsonArray!!.map { it.value!!.jsonArray!!.map {
val server = it!!.jsonObject["result"]!!.jsonPrimitive!!.content val server = it!!.jsonObject["result"]!!.jsonPrimitive!!.content
var url = "" var url = ""
@ -191,7 +204,7 @@ class Gnula : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
url = sc.data().substringAfter("var url = '").substringBefore("';") url = sc.data().substringAfter("var url = '").substringBefore("';")
} }
} }
loadExtractor(url, lang).let { videos -> videoList.addAll(videos) } serverVideoResolver(url, langVal).let { videos -> videoList.addAll(videos) }
} }
} }
} }
@ -200,9 +213,76 @@ class Gnula : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
return videoList return videoList
} }
private fun loadExtractor(url: String, prefix: String = ""): List<Video> { private fun serverVideoResolver(url: String, prefix: String = ""): List<Video> {
val videoList = mutableListOf<Video>() val videoList = mutableListOf<Video>()
val embedUrl = url.lowercase() val embedUrl = url.lowercase()
try {
if (embedUrl.contains("voe")) {
VoeExtractor(client).videoFromUrl(url, prefix = "$prefix Voe:")?.let { videoList.add(it) }
}
if ((embedUrl.contains("amazon") || embedUrl.contains("amz")) && !embedUrl.contains("disable")) {
val body = client.newCall(GET(url)).execute().asJsoup()
if (body.select("script:containsData(var shareId)").toString().isNotBlank()) {
val shareId = body.selectFirst("script:containsData(var shareId)")!!.data()
.substringAfter("shareId = \"").substringBefore("\"")
val amazonApiJson = client.newCall(GET("https://www.amazon.com/drive/v1/shares/$shareId?resourceVersion=V2&ContentType=JSON&asset=ALL"))
.execute().asJsoup()
val epId = amazonApiJson.toString().substringAfter("\"id\":\"").substringBefore("\"")
val amazonApi =
client.newCall(GET("https://www.amazon.com/drive/v1/nodes/$epId/children?resourceVersion=V2&ContentType=JSON&limit=200&sort=%5B%22kind+DESC%22%2C+%22modifiedDate+DESC%22%5D&asset=ALL&tempLink=true&shareId=$shareId"))
.execute().asJsoup()
val videoUrl = amazonApi.toString().substringAfter("\"FOLDER\":").substringAfter("tempLink\":\"").substringBefore("\"")
videoList.add(Video(videoUrl, "$prefix Amazon", videoUrl))
}
}
if (embedUrl.contains("ok.ru") || embedUrl.contains("okru")) {
OkruExtractor(client).videosFromUrl(url, prefix).also(videoList::addAll)
}
if (embedUrl.contains("filemoon") || embedUrl.contains("moonplayer")) {
val vidHeaders = headers.newBuilder()
.add("Origin", "https://${url.toHttpUrl().host}")
.add("Referer", "https://${url.toHttpUrl().host}/")
.build()
FilemoonExtractor(client).videosFromUrl(url, prefix = "$prefix Filemoon:", headers = vidHeaders).also(videoList::addAll)
}
if (embedUrl.contains("uqload")) {
UqloadExtractor(client).videosFromUrl(url, prefix = prefix).also(videoList::addAll)
}
if (embedUrl.contains("mp4upload")) {
Mp4uploadExtractor(client).videosFromUrl(url, headers, prefix = prefix).let { videoList.addAll(it) }
}
if (embedUrl.contains("wishembed") || embedUrl.contains("streamwish") || embedUrl.contains("strwish") || embedUrl.contains("wish")) {
val docHeaders = headers.newBuilder()
.add("Origin", "https://streamwish.to")
.add("Referer", "https://streamwish.to/")
.build()
StreamWishExtractor(client, docHeaders).videosFromUrl(url, videoNameGen = { "$prefix StreamWish:$it" }).also(videoList::addAll)
}
if (embedUrl.contains("doodstream") || embedUrl.contains("dood.")) {
val url2 = url.replace("https://doodstream.com/e/", "https://dood.to/e/")
DoodExtractor(client).videoFromUrl(url2, "$prefix DoodStream", false)?.let { videoList.add(it) }
}
if (embedUrl.contains("streamlare")) {
StreamlareExtractor(client).videosFromUrl(url, prefix = prefix).let { videoList.addAll(it) }
}
if (embedUrl.contains("yourupload") || embedUrl.contains("upload")) {
YourUploadExtractor(client).videoFromUrl(url, headers = headers, prefix = prefix).let { videoList.addAll(it) }
}
if (embedUrl.contains("burstcloud") || embedUrl.contains("burst")) {
BurstCloudExtractor(client).videoFromUrl(url, headers = headers, prefix = prefix).let { videoList.addAll(it) }
}
if (embedUrl.contains("fastream")) {
FastreamExtractor(client).videoFromUrl(url, prefix = "$prefix Fastream:").forEach { videoList.add(it) }
}
if (embedUrl.contains("upstream")) {
UpstreamExtractor(client).videosFromUrl(url, prefix = prefix).let { videoList.addAll(it) }
}
if (embedUrl.contains("streamtape") || embedUrl.contains("stp") || embedUrl.contains("stape")) {
StreamTapeExtractor(client).videoFromUrl(url, quality = "$prefix StreamTape")?.let { videoList.add(it) }
}
if (embedUrl.contains("ahvsh") || embedUrl.contains("streamhide")) {
StreamHideVidExtractor(client).videosFromUrl(url, "$prefix ").let { videoList.addAll(it) }
}
if (embedUrl.contains("tomatomatela")) { if (embedUrl.contains("tomatomatela")) {
runCatching { runCatching {
val mainUrl = url.substringBefore("/embed.html#").substringAfter("https://") val mainUrl = url.substringBefore("/embed.html#").substringAfter("https://")
@ -228,46 +308,22 @@ class Gnula : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
if (status == "200") { videoList.add(Video(file, "$prefix Tomatomatela", file, headers = null)) } if (status == "200") { videoList.add(Video(file, "$prefix Tomatomatela", file, headers = null)) }
} }
} }
if (embedUrl.contains("yourupload")) { } catch (_: Exception) { }
val videos = YourUploadExtractor(client).videoFromUrl(url, headers = headers)
videoList.addAll(videos)
}
if (embedUrl.contains("doodstream") || embedUrl.contains("dood.")) {
DoodExtractor(client).videoFromUrl(url, "$prefix DoodStream", false)?.let { videoList.add(it) }
}
if (embedUrl.contains("okru")) {
videoList.addAll(
OkruExtractor(client).videosFromUrl(url, prefix, true),
)
}
if (embedUrl.contains("voe")) {
VoeExtractor(client).videoFromUrl(url, quality = "$prefix Voex")?.let { videoList.add(it) }
}
if (embedUrl.contains("streamtape")) {
StreamTapeExtractor(client).videoFromUrl(url, quality = "$prefix StreamTape")?.let { videoList.add(it) }
}
return videoList return videoList
} }
override fun List<Video>.sort(): List<Video> { override fun List<Video>.sort(): List<Video> {
return try { val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
val videoSorted = this.sortedWith( val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
compareBy<Video> { it.quality.replace("[0-9]".toRegex(), "") }.thenByDescending { getNumberFromString(it.quality) }, val lang = preferences.getString(PREF_LANGUAGE_KEY, PREF_LANGUAGE_DEFAULT)!!
).toTypedArray() return this.sortedWith(
val userPreferredQuality = preferences.getString("preferred_quality", "Okru:1080p") compareBy(
val preferredIdx = videoSorted.indexOfFirst { x -> x.quality == userPreferredQuality } { it.quality.contains(lang) },
if (preferredIdx != -1) { { it.quality.contains(server, true) },
videoSorted.drop(preferredIdx + 1) { it.quality.contains(quality) },
videoSorted[0] = videoSorted[preferredIdx] { Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 },
} ),
videoSorted.toList() ).reversed()
} catch (e: Exception) {
this
}
}
private fun getNumberFromString(epsStr: String): String {
return epsStr.filter { it.isDigit() }.ifEmpty { "0" }
} }
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
@ -339,32 +395,12 @@ class Gnula : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} }
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
val qualities = arrayOf( ListPreference(screen.context).apply {
"Okru:1080p", "Okru:720p", "Okru:480p", "Okru:360p", "Okru:240p", "Okru:144p", // Okru key = PREF_LANGUAGE_KEY
"Uqload", "Upload", "SolidFiles", "StreamTape", "DoodStream", "Voex", // video servers without resolution
)
val videoQualityPref = ListPreference(screen.context).apply {
key = "preferred_quality"
title = "Preferred quality"
entries = qualities
entryValues = qualities
setDefaultValue("Okru:1080p")
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}
val langPref = ListPreference(screen.context).apply {
key = "preferred_lang"
title = "Preferred language" title = "Preferred language"
entries = arrayOf("Latino", "Español", "Subtitulado", "Todos") entries = LANGUAGE_LIST
entryValues = langValues entryValues = LANGUAGE_LIST
setDefaultValue("latino") setDefaultValue(PREF_LANGUAGE_DEFAULT)
summary = "%s" summary = "%s"
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
@ -373,10 +409,39 @@ class Gnula : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val entry = entryValues[index] as String val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit() preferences.edit().putString(key, entry).commit()
} }
} }.also(screen::addPreference)
screen.addPreference(videoQualityPref) ListPreference(screen.context).apply {
screen.addPreference(langPref) key = PREF_QUALITY_KEY
title = "Preferred quality"
entries = QUALITY_LIST
entryValues = QUALITY_LIST
setDefaultValue(PREF_QUALITY_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_SERVER_KEY
title = "Preferred server"
entries = SERVER_LIST
entryValues = SERVER_LIST
setDefaultValue(PREF_SERVER_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}.also(screen::addPreference)
} }
// Not Used // Not Used

View File

@ -3,10 +3,10 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization' apply plugin: 'kotlinx-serialization'
ext { ext {
extName = 'Hentaila' extName = 'HentaiLA'
pkgNameSuffix = 'es.hentaila' pkgNameSuffix = 'es.hentaila'
extClass = '.Hentaila' extClass = '.Hentaila'
extVersionCode = 17 extVersionCode = 18
libVersion = '13' libVersion = '13'
containsNsfw = true containsNsfw = true
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

@ -31,6 +31,8 @@ import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.IOException import java.io.IOException
import java.lang.Exception import java.lang.Exception
import java.text.SimpleDateFormat
import java.util.Locale
class Hentaila : ConfigurableAnimeSource, AnimeHttpSource() { class Hentaila : ConfigurableAnimeSource, AnimeHttpSource() {
@ -50,36 +52,43 @@ class Hentaila : ConfigurableAnimeSource, AnimeHttpSource() {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
} }
override fun popularAnimeRequest(page: Int) = GET(baseUrl, headers) companion object {
private const val PREF_QUALITY_KEY = "preferred_quality"
private const val PREF_QUALITY_DEFAULT = "1080"
private val QUALITY_LIST = arrayOf("1080", "720", "480", "360")
private const val PREF_SERVER_KEY = "preferred_server"
private const val PREF_SERVER_DEFAULT = "Voe"
private val SERVER_LIST = arrayOf(
"StreamWish",
"Voe",
"Arc",
"YourUpload",
"Mp4Upload",
"BurstCloud",
)
}
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/directorio?filter=popular&p=$page", headers)
override fun popularAnimeParse(response: Response): AnimesPage { override fun popularAnimeParse(response: Response): AnimesPage {
val document = response.asJsoup() val document = response.asJsoup()
val elements = document.select("section.latest-hentais div.slider > div.item") val elements = document.select(".hentais .hentai")
val animes = elements.map { element -> val nextPage = document.select(".pagination .fa-arrow-right").any()
SAnime.create().apply { val animeList = elements.map { element ->
setUrlWithoutDomain(element.select("h2.h-title a").attr("abs:href"))
title = element.selectFirst("h2.h-title a")!!.text()
thumbnail_url = element.selectFirst("figure.bg img")!!.attr("abs:src").replace("/fondos/", "/portadas/")
}
}
return AnimesPage(animes, false)
}
override fun latestUpdatesRequest(page: Int) = GET(baseUrl, headers)
override fun latestUpdatesParse(response: Response): AnimesPage {
val document = response.asJsoup()
val elements = document.select("section.hentai-list div.hentais article.hentai")
val animes = elements.map { element ->
SAnime.create().apply { SAnime.create().apply {
setUrlWithoutDomain(element.select("a").attr("abs:href")) setUrlWithoutDomain(element.select("a").attr("abs:href"))
title = element.selectFirst("h2.h-title")!!.text() title = element.selectFirst(".h-header .h-title")!!.text()
thumbnail_url = element.selectFirst("figure img")!!.attr("abs:src") thumbnail_url = element.selectFirst(".h-thumb img")!!.attr("abs:src").replace("/fondos/", "/portadas/")
} }
} }
return AnimesPage(animes, false) return AnimesPage(animeList, nextPage)
} }
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/directorio?filter=recent&p=$page", headers)
override fun latestUpdatesParse(response: Response) = popularAnimeParse(response)
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val filterList = if (filters.isEmpty()) getFilterList() else filters val filterList = if (filters.isEmpty()) getFilterList() else filters
val genreFilter = filterList.find { it is GenreFilter } as GenreFilter val genreFilter = filterList.find { it is GenreFilter } as GenreFilter
@ -146,7 +155,7 @@ class Hentaila : ConfigurableAnimeSource, AnimeHttpSource() {
} }
} }
val hasNextPage = document.select("a.btn.rnd.npd.fa-arrow-right").isEmpty().not() val hasNextPage = document.select("a.btn.rnd.npd.fa-arrow-right").any()
return AnimesPage(animes, hasNextPage) return AnimesPage(animes, hasNextPage)
} }
@ -179,6 +188,10 @@ class Hentaila : ConfigurableAnimeSource, AnimeHttpSource() {
episode_number = epNum.toFloat() episode_number = epNum.toFloat()
name = "Episodio $epNum" name = "Episodio $epNum"
url = "/ver/$animeId-$epNum" url = "/ver/$animeId-$epNum"
date_upload = try {
val date = it.select(".h-header time").text()
SimpleDateFormat("MMMMM dd, yyyy", Locale.ENGLISH).parse(date).time
} catch (_: Exception) { System.currentTimeMillis() }
} }
episodes.add(episode) episodes.add(episode)
} }
@ -199,10 +212,10 @@ class Hentaila : ConfigurableAnimeSource, AnimeHttpSource() {
when (nameServer.lowercase()) { when (nameServer.lowercase()) {
"streamwish" -> { "streamwish" -> {
videoList.addAll(StreamWishExtractor(client, headers).videosFromUrl(urlServer, "StreamWish ")) videoList.addAll(StreamWishExtractor(client, headers).videosFromUrl(urlServer, videoNameGen = { "StreamWish:$it" }))
} }
"voe" -> { "voe" -> {
val video = VoeExtractor(client).videoFromUrl(urlServer) val video = VoeExtractor(client).videoFromUrl(urlServer, prefix = "Voe:")
if (video != null) videoList.add(video) if (video != null) videoList.add(video)
} }
"arc" -> { "arc" -> {
@ -225,29 +238,15 @@ class Hentaila : ConfigurableAnimeSource, AnimeHttpSource() {
} }
override fun List<Video>.sort(): List<Video> { override fun List<Video>.sort(): List<Video> {
return try { val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
val videoSorted = this.sortedWith( val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
compareBy<Video> { it.quality.replace("[0-9]".toRegex(), "") }.thenByDescending { getNumberFromString(it.quality) }, return this.sortedWith(
).toMutableList() compareBy(
val userPreferredQuality = preferences.getString("preferred_quality", "YourUpload") { it.quality.contains(server, true) },
val newList = mutableListOf<Video>() { it.quality.contains(quality) },
var preferred = 0 { Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 },
for (video in videoSorted) { ),
if (video.quality.startsWith(userPreferredQuality!!)) { ).reversed()
newList.add(preferred, video)
preferred++
} else {
newList.add(video)
}
}
newList.toList()
} catch (e: Exception) {
this
}
}
private fun getNumberFromString(epsStr: String): Int {
return epsStr.filter { it.isDigit() }.ifEmpty { "0" }.toInt()
} }
override fun getFilterList(): AnimeFilterList = AnimeFilterList( override fun getFilterList(): AnimeFilterList = AnimeFilterList(
@ -321,19 +320,12 @@ class Hentaila : ConfigurableAnimeSource, AnimeHttpSource() {
} }
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
val qualities = arrayOf( ListPreference(screen.context).apply {
"YourUpload", key = PREF_SERVER_KEY
"BurstCloud", title = "Preferred server"
"Voe", entries = SERVER_LIST
"StreamWish", entryValues = SERVER_LIST
"Mp4Upload", setDefaultValue(PREF_SERVER_DEFAULT)
)
val videoQualityPref = ListPreference(screen.context).apply {
key = "preferred_quality"
title = "Preferred quality"
entries = qualities
entryValues = qualities
setDefaultValue("YourUpload")
summary = "%s" summary = "%s"
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
@ -342,7 +334,22 @@ class Hentaila : ConfigurableAnimeSource, AnimeHttpSource() {
val entry = entryValues[index] as String val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit() preferences.edit().putString(key, entry).commit()
} }
}.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY
title = "Preferred quality"
entries = QUALITY_LIST
entryValues = QUALITY_LIST
setDefaultValue(PREF_QUALITY_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
} }
screen.addPreference(videoQualityPref) }.also(screen::addPreference)
} }
} }

View File

@ -5,12 +5,13 @@ ext {
extName = 'Jkanime' extName = 'Jkanime'
pkgNameSuffix = 'es.jkanime' pkgNameSuffix = 'es.jkanime'
extClass = '.Jkanime' extClass = '.Jkanime'
extVersionCode = 16 extVersionCode = 17
libVersion = '13' libVersion = '13'
} }
dependencies { dependencies {
implementation(project(':lib-okru-extractor')) implementation(project(':lib-okru-extractor'))
implementation(project(':lib-mixdrop-extractor'))
} }

View File

@ -13,6 +13,7 @@ import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
import eu.kanade.tachiyomi.lib.mixdropextractor.MixDropExtractor
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
@ -23,7 +24,6 @@ import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.lang.Exception
class Jkanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() { class Jkanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
@ -41,6 +41,26 @@ class Jkanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
} }
companion object {
private const val PREF_LANGUAGE_KEY = "preferred_language"
private const val PREF_LANGUAGE_DEFAULT = "[JAP]"
private val LANGUAGE_LIST = arrayOf("[JAP]", "[LAT]")
private const val PREF_QUALITY_KEY = "preferred_quality"
private const val PREF_QUALITY_DEFAULT = "1080"
private val QUALITY_LIST = arrayOf("1080", "720", "480", "360")
private const val PREF_SERVER_KEY = "preferred_server"
private const val PREF_SERVER_DEFAULT = "Nozomi"
private val SERVER_LIST = arrayOf(
"Okru",
"Xtreme S",
"HentaiJk",
"Nozomi",
"Desu",
)
}
override fun popularAnimeSelector(): String = "div.col-lg-12 div.list" override fun popularAnimeSelector(): String = "div.col-lg-12 div.list"
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/top/") override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/top/")
@ -99,7 +119,8 @@ class Jkanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val videos = mutableListOf<Video>() val videos = mutableListOf<Video>()
document.select("div.col-lg-12.rounded.bg-servers.text-white.p-3.mt-2 a").forEach { it -> document.select("div.col-lg-12.rounded.bg-servers.text-white.p-3.mt-2 a").forEach { it ->
val serverId = it.attr("data-id") val serverId = it.attr("data-id")
val lang = if (it.attr("class").contains("lg_3")) "[LAT]" else "" val langClass = it.attr("class")
val lang = if (langClass.contains("lg_3")) "[LAT]" else if (langClass.contains("lg_1")) "[JAP]" else ""
val scriptServers = document.selectFirst("script:containsData(var video = [];)")!! val scriptServers = document.selectFirst("script:containsData(var video = [];)")!!
val url = scriptServers.data().substringAfter("video[$serverId] = '<iframe class=\"player_conte\" src=\"") val url = scriptServers.data().substringAfter("video[$serverId] = '<iframe class=\"player_conte\" src=\"")
.substringBefore("\"") .substringBefore("\"")
@ -107,12 +128,15 @@ class Jkanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
.replace("/jkvmixdrop.php?u=", "https://mixdrop.co/e/") .replace("/jkvmixdrop.php?u=", "https://mixdrop.co/e/")
.replace("/jk.php?u=", "$baseUrl/") .replace("/jk.php?u=", "$baseUrl/")
try {
when { when {
"ok" in url -> OkruExtractor(client).videosFromUrl(url, lang).forEach { videos.add(it) } "ok" in url -> OkruExtractor(client).videosFromUrl(url, "$lang ").forEach { videos.add(it) }
"stream/jkmedia" in url -> videos.add(Video(url, "${lang}Xtreme S", url)) "mixdrop" in url -> MixDropExtractor(client).videosFromUrl(url, prefix = "$lang ").forEach { videos.add(it) }
"um2.php" in url -> JkanimeExtractor(client).getNozomiFromUrl(baseUrl + url, lang).let { if (it != null) videos.add(it) } "stream/jkmedia" in url -> videos.add(Video(url, "$lang Xtreme S", url))
"um.php" in url -> JkanimeExtractor(client).getDesuFromUrl(baseUrl + url, lang).let { if (it != null) videos.add(it) } "um2.php" in url -> JkanimeExtractor(client).getNozomiFromUrl(baseUrl + url, "$lang ").let { if (it != null) videos.add(it) }
"um.php" in url -> JkanimeExtractor(client).getDesuFromUrl(baseUrl + url, "$lang ").let { if (it != null) videos.add(it) }
} }
} catch (_: Exception) {}
} }
return videos return videos
} }
@ -121,21 +145,18 @@ class Jkanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun videoUrlParse(document: Document) = throw Exception("not used") override fun videoUrlParse(document: Document) = throw Exception("not used")
override fun videoFromElement(element: Element) = throw Exception("not used") override fun videoFromElement(element: Element) = throw Exception("not used")
private fun List<Video>.sortIfContains(item: String): List<Video> {
val newList = mutableListOf<Video>()
for (video in this) {
if (item in video.quality) {
newList.add(0, video)
} else {
newList.add(video)
}
}
return newList
}
override fun List<Video>.sort(): List<Video> { override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString("preferred_quality", "Nozomi")!! val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
return sortIfContains(quality) val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
val lang = preferences.getString(PREF_LANGUAGE_KEY, PREF_LANGUAGE_DEFAULT)!!
return this.sortedWith(
compareBy(
{ it.quality.contains(lang) },
{ it.quality.contains(server, true) },
{ it.quality.contains(quality) },
{ Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 },
),
).reversed()
} }
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
@ -248,12 +269,10 @@ class Jkanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun latestUpdatesFromElement(element: Element): SAnime { override fun latestUpdatesFromElement(element: Element): SAnime {
val anime = SAnime.create() val anime = SAnime.create()
anime.setUrlWithoutDomain( anime.setUrlWithoutDomain(element.select(".custom_thumb2 > a").attr("abs:href"))
element.select("div.row.g-0 div.col-md-5.custom_thumb2 a").attr("href"), anime.title = element.select(".card-title > a").text()
) anime.thumbnail_url = element.select(".custom_thumb2 a img").attr("abs:src")
anime.title = element.select("div.row.g-0 div.col-md-7 div.card-body h5.card-title a").text() anime.description = element.select(".synopsis").text()
anime.thumbnail_url = element.select("div.row.g-0 div.col-md-5.custom_thumb2 a img").attr("src")
anime.description = element.select("div.row.g-0 div.col-md-7 div.card-body p.card-text.synopsis").text()
return anime return anime
} }
@ -393,16 +412,12 @@ class Jkanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} }
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
val qualities = arrayOf( ListPreference(screen.context).apply {
"Okru:1080p", "Okru:720p", "Okru:480p", "Okru:360p", "Okru:240p", // Okru key = PREF_LANGUAGE_KEY
"Xtreme S", "HentaiJk", "Nozomi", "Desu", // video servers without resolution title = "Preferred language"
) entries = LANGUAGE_LIST
val videoQualityPref = ListPreference(screen.context).apply { entryValues = LANGUAGE_LIST
key = "preferred_quality" setDefaultValue(PREF_LANGUAGE_DEFAULT)
title = "Preferred quality"
entries = qualities
entryValues = qualities
setDefaultValue("Nozomi")
summary = "%s" summary = "%s"
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
@ -411,7 +426,38 @@ class Jkanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val entry = entryValues[index] as String val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit() preferences.edit().putString(key, entry).commit()
} }
}.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY
title = "Preferred quality"
entries = QUALITY_LIST
entryValues = QUALITY_LIST
setDefaultValue(PREF_QUALITY_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
} }
screen.addPreference(videoQualityPref) }.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_SERVER_KEY
title = "Preferred server"
entries = SERVER_LIST
entryValues = SERVER_LIST
setDefaultValue(PREF_SERVER_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}.also(screen::addPreference)
} }
} }

View File

@ -5,7 +5,7 @@ ext {
extName = 'LocoPelis' extName = 'LocoPelis'
pkgNameSuffix = 'es.locopelis' pkgNameSuffix = 'es.locopelis'
extClass = '.LocoPelis' extClass = '.LocoPelis'
extVersionCode = 17 extVersionCode = 18
libVersion = '13' libVersion = '13'
} }

View File

@ -16,7 +16,6 @@ import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.json.Json
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
@ -24,7 +23,6 @@ import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import kotlin.Exception import kotlin.Exception
@ -40,12 +38,20 @@ class LocoPelis : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val client: OkHttpClient = network.cloudflareClient override val client: OkHttpClient = network.cloudflareClient
private val json: Json by injectLazy()
private val preferences: SharedPreferences by lazy { private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
} }
companion object {
private const val PREF_QUALITY_KEY = "preferred_quality"
private const val PREF_QUALITY_DEFAULT = "1080"
private val QUALITY_LIST = arrayOf("1080", "720", "480", "360")
private const val PREF_SERVER_KEY = "preferred_server"
private const val PREF_SERVER_DEFAULT = "DoodStream"
private val SERVER_LIST = arrayOf("Okru", "DoodStream", "StreamTape")
}
override fun popularAnimeSelector(): String = "ul.peliculas li.peli_bx" override fun popularAnimeSelector(): String = "ul.peliculas li.peli_bx"
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/pelicula/peliculas-mas-vistas?page=$page") override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/pelicula/peliculas-mas-vistas?page=$page")
@ -91,13 +97,16 @@ class LocoPelis : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
document.select(".tab_container .tab_content iframe").forEach { iframe -> document.select(".tab_container .tab_content iframe").forEach { iframe ->
val url = iframe.attr("src") val url = iframe.attr("src")
val embedUrl = url.lowercase() val embedUrl = url.lowercase()
if (url.lowercase().contains("streamtape")) { if (embedUrl.contains("streamtape")) {
val video = StreamTapeExtractor(client).videoFromUrl(url, "Streamtape") val video = StreamTapeExtractor(client).videoFromUrl(url, "StreamTape")
if (video != null) { if (video != null) {
videoList.add(video) videoList.add(video)
} }
} }
if (url.lowercase().contains("doodstream") || url.lowercase().contains("dood")) { if (embedUrl.contains("doodstream") ||
embedUrl.contains("dood") ||
embedUrl.contains("ds2play")
) {
val video = try { val video = try {
DoodExtractor(client).videoFromUrl(url, "DoodStream", true) DoodExtractor(client).videoFromUrl(url, "DoodStream", true)
} catch (e: Exception) { } catch (e: Exception) {
@ -107,7 +116,7 @@ class LocoPelis : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
videoList.add(video) videoList.add(video)
} }
} }
if (url.lowercase().contains("okru")) { if (embedUrl.contains("okru") || embedUrl.contains("ok.ru")) {
val videos = OkruExtractor(client).videosFromUrl(url) val videos = OkruExtractor(client).videosFromUrl(url)
videoList.addAll(videos) videoList.addAll(videos)
} }
@ -121,23 +130,6 @@ class LocoPelis : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun videoFromElement(element: Element) = throw Exception("not used") override fun videoFromElement(element: Element) = throw Exception("not used")
override fun List<Video>.sort(): List<Video> {
return try {
val videoSorted = this.sortedWith(
compareBy<Video> { it.quality.replace("[0-9]".toRegex(), "") }.thenByDescending { getNumberFromString(it.quality) },
).toTypedArray()
val userPreferredQuality = preferences.getString("preferred_quality", "DoodStream")
val preferredIdx = videoSorted.indexOfFirst { x -> x.quality == userPreferredQuality }
if (preferredIdx != -1) {
videoSorted.drop(preferredIdx + 1)
videoSorted[0] = videoSorted[preferredIdx]
}
videoSorted.toList()
} catch (e: Exception) {
this
}
}
private fun getNumberFromString(epsStr: String): String { private fun getNumberFromString(epsStr: String): String {
return epsStr.filter { it.isDigit() }.ifEmpty { "0" } return epsStr.filter { it.isDigit() }.ifEmpty { "0" }
} }
@ -242,25 +234,25 @@ class LocoPelis : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun latestUpdatesSelector() = popularAnimeSelector() override fun latestUpdatesSelector() = popularAnimeSelector()
override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
return this.sortedWith(
compareBy(
{ it.quality.contains(server, true) },
{ it.quality.contains(quality) },
{ Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 },
),
).reversed()
}
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
val videoQualityPref = ListPreference(screen.context).apply { ListPreference(screen.context).apply {
key = "preferred_quality" key = PREF_SERVER_KEY
title = "Preferred quality" title = "Preferred server"
entries = arrayOf( entries = SERVER_LIST
"Okru:1080p", "Okru:720p", "Okru:480p", "Okru:360p", "Okru:240p", "Okru:144p", // Okru entryValues = SERVER_LIST
"YourUpload", "DoodStream", "StreamTape", setDefaultValue(PREF_SERVER_DEFAULT)
) // video servers without resolution
entryValues = arrayOf(
"Okru:1080p",
"Okru:720p",
"Okru:480p",
"Okru:360p",
"Okru:240p",
"Okru:144p", // Okru
"DoodStream",
"StreamTape",
) // video servers without resolution
setDefaultValue("DoodStream")
summary = "%s" summary = "%s"
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
@ -269,7 +261,22 @@ class LocoPelis : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val entry = entryValues[index] as String val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit() preferences.edit().putString(key, entry).commit()
} }
}.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY
title = "Preferred quality"
entries = QUALITY_LIST
entryValues = QUALITY_LIST
setDefaultValue(PREF_QUALITY_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
} }
screen.addPreference(videoQualityPref) }.also(screen::addPreference)
} }
} }

View File

@ -6,7 +6,7 @@ ext {
extName = 'MetroSeries' extName = 'MetroSeries'
pkgNameSuffix = 'es.metroseries' pkgNameSuffix = 'es.metroseries'
extClass = '.MetroSeries' extClass = '.MetroSeries'
extVersionCode = 1 extVersionCode = 2
libVersion = '13' libVersion = '13'
} }

View File

@ -21,14 +21,17 @@ import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.json.Json import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
@ -40,8 +43,6 @@ class MetroSeries : ConfigurableAnimeSource, AnimeHttpSource() {
override val lang = "es" override val lang = "es"
private val json: Json by injectLazy()
override val supportsLatest = false override val supportsLatest = false
override val client: OkHttpClient = network.cloudflareClient override val client: OkHttpClient = network.cloudflareClient
@ -50,6 +51,25 @@ class MetroSeries : ConfigurableAnimeSource, AnimeHttpSource() {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
} }
companion object {
private const val PREF_QUALITY_KEY = "preferred_quality"
private const val PREF_QUALITY_DEFAULT = "1080"
private val QUALITY_LIST = arrayOf("1080", "720", "480", "360")
private const val PREF_SERVER_KEY = "preferred_server"
private const val PREF_SERVER_DEFAULT = "YourUpload"
private val SERVER_LIST = arrayOf(
"YourUpload",
"BurstCloud",
"Voe",
"StreamWish",
"Mp4Upload",
"Fastream",
"Upstream",
"Filemoon",
)
}
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/series/page/$page", headers) override fun popularAnimeRequest(page: Int) = GET("$baseUrl/series/page/$page", headers)
override fun popularAnimeParse(response: Response): AnimesPage { override fun popularAnimeParse(response: Response): AnimesPage {
@ -86,14 +106,29 @@ class MetroSeries : ConfigurableAnimeSource, AnimeHttpSource() {
} }
override fun episodeListParse(response: Response): List<SEpisode> { override fun episodeListParse(response: Response): List<SEpisode> {
val episodes = mutableListOf<SEpisode>()
val document = response.asJsoup() val document = response.asJsoup()
document.select(".season-list li a") val referer = response.request.url.toString()
.sortedByDescending { it.attr("data-season") }.map { val chunkSize = Runtime.getRuntime().availableProcessors()
val post = it.attr("data-post")
val season = it.attr("data-season")
val objectNumber = document.select("#aa-season").attr("data-object") val objectNumber = document.select("#aa-season").attr("data-object")
val episodes = document.select(".season-list li a")
.sortedByDescending { it.attr("data-season") }
.chunked(chunkSize).flatMap { chunk ->
chunk.parallelMap { season ->
runCatching {
val pages = getDetailSeason(season, objectNumber, referer)
getPageEpisodeList(pages, referer, objectNumber, season.attr("data-season"))
}.getOrNull()
}.filterNotNull().flatten()
}.sortedByDescending {
it.name.substringBeforeLast("-")
}.ifEmpty { emptyList() }
return episodes
}
private fun getDetailSeason(element: org.jsoup.nodes.Element, objectNumber: String, referer: String): IntRange {
try {
val post = element.attr("data-post")
val season = element.attr("data-season")
val formBody = FormBody.Builder() val formBody = FormBody.Builder()
.add("action", "action_select_season") .add("action", "action_select_season")
.add("season", season) .add("season", season)
@ -105,26 +140,51 @@ class MetroSeries : ConfigurableAnimeSource, AnimeHttpSource() {
.url("https://metroseries.net/wp-admin/admin-ajax.php") .url("https://metroseries.net/wp-admin/admin-ajax.php")
.post(formBody) .post(formBody)
.header("Origin", baseUrl) .header("Origin", baseUrl)
.header("Referer", response.request.url.toString()) .header("Referer", referer)
.header("Content-Type", "application/x-www-form-urlencoded") .header("Content-Type", "application/x-www-form-urlencoded")
.build() .build()
val docEpisodes = client.newCall(request).execute().asJsoup() val detail = client.newCall(request).execute().asJsoup()
docEpisodes.select(".episodes-list li a").reversed().map { val firstPage = try { detail.selectFirst("#aa-season > nav > span.page-numbers")?.text()?.toInt() ?: 1 } catch (_: Exception) { 1 }
val epNumber = it.ownText().substringAfter("x").substringBefore("").trim() val lastPage = try { detail.select(".pagination a.page-numbers:not(.next)").last()?.text()?.toInt() ?: firstPage } catch (_: Exception) { firstPage }
val episode = SEpisode.create().apply {
setUrlWithoutDomain(it.attr("abs:href")) return firstPage.rangeTo(lastPage)
name = "T$season - E$epNumber - ${it.ownText().substringAfter("").trim()}" } catch (_: Exception) {
date_upload = try { return 1..1
SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH).parse(it.select("span").text()).time
} catch (_: Exception) { System.currentTimeMillis() }
}
episodes.add(episode)
} }
} }
private fun getPageEpisodeList(pages: IntRange, referer: String, objectNumber: String, season: String): List<SEpisode> {
val episodes = mutableListOf<SEpisode>()
try {
pages.parallelMap {
val formBody = FormBody.Builder()
.add("action", "action_pagination_ep")
.add("page", "$it")
.add("object", objectNumber)
.add("season", season)
.build()
val requestPage = Request.Builder()
.url("https://metroseries.net/wp-admin/admin-ajax.php")
.post(formBody)
.header("authority", baseUrl.toHttpUrl().host)
.header("Origin", "https://${baseUrl.toHttpUrl().host}")
.header("Referer", referer)
.header("Content-Type", "application/x-www-form-urlencoded")
.build()
client.newCall(requestPage).execute().parseAsEpisodeList().also(episodes::addAll)
}
} catch (_: Exception) { }
return episodes return episodes
} }
private fun <A, B> Iterable<A>.parallelMap(f: suspend (A) -> B): List<B> =
runBlocking {
map { async(Dispatchers.Default) { f(it) } }.awaitAll()
}
override fun videoListParse(response: Response): List<Video> { override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup() val document = response.asJsoup()
val videoList = mutableListOf<Video>() val videoList = mutableListOf<Video>()
@ -180,7 +240,7 @@ class MetroSeries : ConfigurableAnimeSource, AnimeHttpSource() {
BurstCloudExtractor(client).videoFromUrl(src, headers = headers).let { videoList.addAll(it) } BurstCloudExtractor(client).videoFromUrl(src, headers = headers).let { videoList.addAll(it) }
} }
if (src.contains("filemoon") || src.contains("moonplayer")) { if (src.contains("filemoon") || src.contains("moonplayer")) {
FilemoonExtractor(client).videosFromUrl(src, headers = headers).let { videoList.addAll(it) } FilemoonExtractor(client).videosFromUrl(src, headers = headers, prefix = "Filemoon:").let { videoList.addAll(it) }
} }
} catch (_: Exception) {} } catch (_: Exception) {}
} }
@ -189,41 +249,24 @@ class MetroSeries : ConfigurableAnimeSource, AnimeHttpSource() {
} }
override fun List<Video>.sort(): List<Video> { override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString("preferred_quality", "Fastream:1080p") val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
if (quality != null) { val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
val newList = mutableListOf<Video>() return this.sortedWith(
var preferred = 0 compareBy(
for (video in this) { { it.quality.contains(server, true) },
if (video.quality == quality) { { it.quality.contains(quality) },
newList.add(preferred, video) { Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 },
preferred++ ),
} else { ).reversed()
newList.add(video)
}
}
return newList
}
return this
} }
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
val qualities = arrayOf( ListPreference(screen.context).apply {
"YourUpload", key = PREF_QUALITY_KEY
"BurstCloud",
"Voe",
"StreamWish",
"Mp4Upload",
"Fastream:1080p",
"Fastream:720p",
"Fastream:480p",
"Fastream:360p",
)
val videoQualityPref = ListPreference(screen.context).apply {
key = "preferred_quality"
title = "Preferred quality" title = "Preferred quality"
entries = qualities entries = QUALITY_LIST
entryValues = qualities entryValues = QUALITY_LIST
setDefaultValue("Fastream:1080p") setDefaultValue(PREF_QUALITY_DEFAULT)
summary = "%s" summary = "%s"
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
@ -232,7 +275,39 @@ class MetroSeries : ConfigurableAnimeSource, AnimeHttpSource() {
val entry = entryValues[index] as String val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit() preferences.edit().putString(key, entry).commit()
} }
}.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_SERVER_KEY
title = "Preferred server"
entries = SERVER_LIST
entryValues = SERVER_LIST
setDefaultValue(PREF_SERVER_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}.also(screen::addPreference)
}
private fun Response.parseAsEpisodeList(): List<SEpisode> {
return asJsoup().select(".episodes-list li a").reversed().mapIndexed { idx, it ->
val epNumber = try { it.ownText().substringAfter("x").substringBefore("").trim() } catch (_: Exception) { "${idx + 1}" }
val season = it.ownText().substringBefore("x").trim()
SEpisode.create().apply {
setUrlWithoutDomain(it.attr("abs:href"))
name = "T$season - E$epNumber - ${it.ownText().substringAfter("").trim()}"
episode_number = epNumber.toFloat()
date_upload = try {
SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH).parse(it.select("span").text()).time
} catch (_: Exception) {
System.currentTimeMillis()
}
}
} }
screen.addPreference(videoQualityPref)
} }
} }

View File

@ -6,7 +6,7 @@ ext {
extName = 'PelisForte' extName = 'PelisForte'
pkgNameSuffix = 'es.pelisforte' pkgNameSuffix = 'es.pelisforte'
extClass = '.PelisForte' extClass = '.PelisForte'
extVersionCode = 3 extVersionCode = 4
libVersion = '13' libVersion = '13'
} }

View File

@ -62,8 +62,8 @@ open class PelisForte : ConfigurableAnimeSource, AnimeHttpSource() {
private const val PREF_SERVER_KEY = "preferred_server" private const val PREF_SERVER_KEY = "preferred_server"
private const val PREF_SERVER_DEFAULT = "StreamWish" private const val PREF_SERVER_DEFAULT = "StreamWish"
private val SERVER_LIST = arrayOf( private val SERVER_LIST = arrayOf(
"YourUpload", "BurstCloud", "Voe", "Mp4Upload", "Doodstream", "YourUpload", "Voe", "Mp4Upload", "Doodstream",
"Upload", "BurstCloud", "Upstream", "StreamTape", "Doodstream", "Upload", "BurstCloud", "Upstream", "StreamTape",
"Fastream", "Filemoon", "StreamWish", "Okru", "Fastream", "Filemoon", "StreamWish", "Okru",
) )
} }
@ -183,7 +183,7 @@ open class PelisForte : ConfigurableAnimeSource, AnimeHttpSource() {
val embedUrl = url.lowercase() val embedUrl = url.lowercase()
try { try {
if (embedUrl.contains("voe")) { if (embedUrl.contains("voe")) {
VoeExtractor(client).videoFromUrl(url, "${prefix}Voe")?.let { videoList.add(it) } VoeExtractor(client).videoFromUrl(url, prefix = "${prefix}Voe:")?.let { videoList.add(it) }
} }
if (embedUrl.contains("ok.ru") || embedUrl.contains("okru")) { if (embedUrl.contains("ok.ru") || embedUrl.contains("okru")) {
OkruExtractor(client).videosFromUrl(url, prefix).also(videoList::addAll) OkruExtractor(client).videosFromUrl(url, prefix).also(videoList::addAll)
@ -203,7 +203,7 @@ open class PelisForte : ConfigurableAnimeSource, AnimeHttpSource() {
val docHeaders = headers.newBuilder() val docHeaders = headers.newBuilder()
.add("Referer", referer) .add("Referer", referer)
.build() .build()
StreamWishExtractor(client, docHeaders).videosFromUrl(url, "${prefix}StreamWish").also(videoList::addAll) StreamWishExtractor(client, docHeaders).videosFromUrl(url, videoNameGen = { "${prefix}StreamWish:$it" }).also(videoList::addAll)
} }
if (embedUrl.contains("doodstream") || embedUrl.contains("dood.")) { if (embedUrl.contains("doodstream") || embedUrl.contains("dood.")) {
DoodExtractor(client).videoFromUrl(url, "${prefix}DoodStream", false)?.let { videoList.add(it) } DoodExtractor(client).videoFromUrl(url, "${prefix}DoodStream", false)?.let { videoList.add(it) }
@ -282,6 +282,22 @@ open class PelisForte : ConfigurableAnimeSource, AnimeHttpSource() {
} }
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply {
key = PREF_LANGUAGE_KEY
title = "Preferred language"
entries = LANGUAGE_LIST
entryValues = LANGUAGE_LIST
setDefaultValue(PREF_LANGUAGE_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}.also(screen::addPreference)
ListPreference(screen.context).apply { ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY key = PREF_QUALITY_KEY
title = "Preferred quality" title = "Preferred quality"
@ -313,21 +329,5 @@ open class PelisForte : ConfigurableAnimeSource, AnimeHttpSource() {
preferences.edit().putString(key, entry).commit() preferences.edit().putString(key, entry).commit()
} }
}.also(screen::addPreference) }.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_LANGUAGE_KEY
title = "Preferred server"
entries = LANGUAGE_LIST
entryValues = LANGUAGE_LIST
setDefaultValue(PREF_LANGUAGE_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}.also(screen::addPreference)
} }
} }

View File

@ -5,7 +5,7 @@ ext {
extName = 'Pelisplushd' extName = 'Pelisplushd'
pkgNameSuffix = 'es.pelisplushd' pkgNameSuffix = 'es.pelisplushd'
extClass = '.PelisplushdFactory' extClass = '.PelisplushdFactory'
extVersionCode = 44 extVersionCode = 45
libVersion = '13' libVersion = '13'
} }
@ -20,6 +20,11 @@ dependencies {
implementation(project(':lib-dood-extractor')) implementation(project(':lib-dood-extractor'))
implementation(project(':lib-voe-extractor')) implementation(project(':lib-voe-extractor'))
implementation(project(':lib-okru-extractor')) implementation(project(':lib-okru-extractor'))
implementation(project(':lib-mp4upload-extractor'))
implementation(project(':lib-mixdrop-extractor'))
implementation(project(':lib-burstcloud-extractor'))
implementation(project(':lib-fastream-extractor'))
implementation(project(':lib-upstream-extractor'))
implementation "dev.datlag.jsunpacker:jsunpacker:1.0.1" implementation "dev.datlag.jsunpacker:jsunpacker:1.0.1"
} }

View File

@ -5,6 +5,7 @@ import android.content.SharedPreferences
import android.util.Base64 import android.util.Base64
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.es.pelisplushd.extractors.StreamHideExtractor
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.animesource.model.AnimeFilter import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
@ -12,15 +13,25 @@ import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
import eu.kanade.tachiyomi.lib.burstcloudextractor.BurstCloudExtractor
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
import eu.kanade.tachiyomi.lib.fastreamextractor.FastreamExtractor
import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
import eu.kanade.tachiyomi.lib.streamlareextractor.StreamlareExtractor import eu.kanade.tachiyomi.lib.streamlareextractor.StreamlareExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
import eu.kanade.tachiyomi.lib.upstreamextractor.UpstreamExtractor
import eu.kanade.tachiyomi.lib.uqloadextractor.UqloadExtractor
import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
@ -28,6 +39,7 @@ import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
open class Pelisplushd(override val name: String, override val baseUrl: String) : ConfigurableAnimeSource, ParsedAnimeHttpSource() { open class Pelisplushd(override val name: String, override val baseUrl: String) : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
@ -37,22 +49,36 @@ open class Pelisplushd(override val name: String, override val baseUrl: String)
override val client: OkHttpClient = network.cloudflareClient override val client: OkHttpClient = network.cloudflareClient
private val json: Json by injectLazy()
val preferences: SharedPreferences by lazy { val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000) Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
} }
companion object {
const val PREF_QUALITY_KEY = "preferred_quality"
const val PREF_QUALITY_DEFAULT = "1080"
val QUALITY_LIST = arrayOf("1080", "720", "480", "360")
private const val PREF_SERVER_KEY = "preferred_server"
private const val PREF_SERVER_DEFAULT = "Voe"
private val SERVER_LIST = arrayOf(
"YourUpload", "BurstCloud", "Voe", "Mp4Upload", "Doodstream",
"Upload", "BurstCloud", "Upstream", "StreamTape", "Amazon",
"Fastream", "Filemoon", "StreamWish", "Okru", "Streamlare",
)
}
override fun popularAnimeSelector(): String = "div.Posters a.Posters-link" override fun popularAnimeSelector(): String = "div.Posters a.Posters-link"
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/series?page=$page") override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/series?page=$page")
override fun popularAnimeFromElement(element: Element): SAnime { override fun popularAnimeFromElement(element: Element): SAnime {
val anime = SAnime.create() return SAnime.create().apply {
anime.setUrlWithoutDomain( setUrlWithoutDomain(element.select("a").attr("abs:href"))
element.select("a").attr("href"), title = element.select("a div.listing-content p").text()
) thumbnail_url = element.select("a img").attr("src").replace("/w154/", "/w200/")
anime.title = element.select("a div.listing-content p").text() }
anime.thumbnail_url = element.select("a img").attr("src").replace("/w154/", "/w200/")
return anime
} }
override fun popularAnimeNextPageSelector(): String = "a.page-link" override fun popularAnimeNextPageSelector(): String = "a.page-link"
@ -62,19 +88,18 @@ open class Pelisplushd(override val name: String, override val baseUrl: String)
val jsoup = response.asJsoup() val jsoup = response.asJsoup()
if (response.request.url.toString().contains("/pelicula/")) { if (response.request.url.toString().contains("/pelicula/")) {
val episode = SEpisode.create().apply { val episode = SEpisode.create().apply {
val epnum = 1 episode_number = 1F
episode_number = epnum.toFloat()
name = "PELÍCULA" name = "PELÍCULA"
setUrlWithoutDomain(response.request.url.toString())
} }
episode.setUrlWithoutDomain(response.request.url.toString())
episodes.add(episode) episodes.add(episode)
} else { } else {
jsoup.select("div.tab-content div a").forEachIndexed { index, element -> jsoup.select("div.tab-content div a").forEachIndexed { index, element ->
val epNum = index + 1 val episode = SEpisode.create().apply {
val episode = SEpisode.create() episode_number = (index + 1).toFloat()
episode.episode_number = epNum.toFloat() name = element.text()
episode.name = element.text() setUrlWithoutDomain(element.attr("abs:href"))
episode.setUrlWithoutDomain(element.attr("href")) }
episodes.add(episode) episodes.add(episode)
} }
} }
@ -93,8 +118,6 @@ open class Pelisplushd(override val name: String, override val baseUrl: String)
val apiUrl = data.substringAfter("video[1] = '", "").substringBefore("';", "") val apiUrl = data.substringAfter("video[1] = '", "").substringBefore("';", "")
val alternativeServers = document.select("ul.TbVideoNv.nav.nav-tabs li:not(:first-child)") val alternativeServers = document.select("ul.TbVideoNv.nav.nav-tabs li:not(:first-child)")
if (apiUrl.isNotEmpty()) { if (apiUrl.isNotEmpty()) {
// val domainRegex = Regex("^(?:https?:\\/\\/)?(?:[^@\\/\\n]+@)?(?:www\\.)?([^:\\/?\\n]+)")
// val domainUrl = domainRegex.findAll(apiUrl).firstOrNull()?.value
val apiResponse = client.newCall(GET(apiUrl)).execute().asJsoup() val apiResponse = client.newCall(GET(apiUrl)).execute().asJsoup()
val encryptedList = apiResponse!!.select("#PlayerDisplay div[class*=\"OptionsLangDisp\"] div[class*=\"ODDIV\"] div[class*=\"OD\"] li") val encryptedList = apiResponse!!.select("#PlayerDisplay div[class*=\"OptionsLangDisp\"] div[class*=\"ODDIV\"] div[class*=\"OD\"] li")
val regIsUrl = "https?:\\/\\/(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_\\+.~#?&//=]*)".toRegex() val regIsUrl = "https?:\\/\\/(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_\\+.~#?&//=]*)".toRegex()
@ -114,12 +137,12 @@ open class Pelisplushd(override val name: String, override val baseUrl: String)
} }
if (!url.contains("?data=")) { if (!url.contains("?data=")) {
serverVideoResolver(url, server)?.forEach { video -> videoList.add(video) } serverVideoResolver(url)?.forEach { video -> videoList.add(video) }
} else { } else {
val apiPageSoup = client.newCall(GET(url)).execute().asJsoup() val apiPageSoup = client.newCall(GET(url)).execute().asJsoup()
val realUrl = apiPageSoup.selectFirst("iframe")?.attr("src") val realUrl = apiPageSoup.selectFirst("iframe")?.attr("src")
if (realUrl != null) { if (realUrl != null) {
serverVideoResolver(realUrl, server)?.forEach { video -> videoList.add(video) } serverVideoResolver(realUrl)?.forEach { video -> videoList.add(video) }
} }
} }
} }
@ -140,66 +163,109 @@ open class Pelisplushd(override val name: String, override val baseUrl: String)
"upload", "uqload" -> { serverUrl = "https://uqload.com/embed-$urlId.html" } "upload", "uqload" -> { serverUrl = "https://uqload.com/embed-$urlId.html" }
} }
} }
serverVideoResolver(serverUrl, serverName)?.forEach { video -> videoList.add(video) } serverVideoResolver(serverUrl)?.forEach { video -> videoList.add(video) }
} }
} }
return videoList return videoList
} }
private fun bytesToHex(bytes: ByteArray): String { private fun serverVideoResolver(url: String): List<Video> {
val hexArray = "0123456789ABCDEF".toCharArray()
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)
}
private fun serverVideoResolver(url: String, server: String): List<Video>? {
val videoList = mutableListOf<Video>() val videoList = mutableListOf<Video>()
val embedUrl = url.lowercase()
try { try {
if (server.lowercase() == "stp") { if (embedUrl.contains("voe")) {
StreamTapeExtractor(client).videoFromUrl(url, "StreamTape")?.let { videoList.add(it) } VoeExtractor(client).videoFromUrl(url, prefix = "Voe:")?.let { videoList.add(it) }
} else if (server.lowercase() == "uwu") { }
if (!url.contains("disable")) { if ((embedUrl.contains("amazon") || embedUrl.contains("amz")) && !embedUrl.contains("disable")) {
val body = client.newCall(GET(url)).execute().asJsoup() val body = client.newCall(GET(url)).execute().asJsoup()
if (body.select("script:containsData(var shareId)").toString() if (body.select("script:containsData(var shareId)").toString().isNotBlank()) {
.isNotBlank() val shareId = body.selectFirst("script:containsData(var shareId)")!!.data()
) {
val shareId =
body.selectFirst("script:containsData(var shareId)")!!.data()
.substringAfter("shareId = \"").substringBefore("\"") .substringAfter("shareId = \"").substringBefore("\"")
val amazonApiJson = val amazonApiJson = client.newCall(GET("https://www.amazon.com/drive/v1/shares/$shareId?resourceVersion=V2&ContentType=JSON&asset=ALL"))
client.newCall(GET("https://www.amazon.com/drive/v1/shares/$shareId?resourceVersion=V2&ContentType=JSON&asset=ALL"))
.execute().asJsoup() .execute().asJsoup()
val epId = amazonApiJson.toString().substringAfter("\"id\":\"") val epId = amazonApiJson.toString().substringAfter("\"id\":\"").substringBefore("\"")
.substringBefore("\"")
val amazonApi = val amazonApi =
client.newCall(GET("https://www.amazon.com/drive/v1/nodes/$epId/children?resourceVersion=V2&ContentType=JSON&limit=200&sort=%5B%22kind+DESC%22%2C+%22modifiedDate+DESC%22%5D&asset=ALL&tempLink=true&shareId=$shareId")) client.newCall(GET("https://www.amazon.com/drive/v1/nodes/$epId/children?resourceVersion=V2&ContentType=JSON&limit=200&sort=%5B%22kind+DESC%22%2C+%22modifiedDate+DESC%22%5D&asset=ALL&tempLink=true&shareId=$shareId"))
.execute().asJsoup() .execute().asJsoup()
val videoUrl = amazonApi.toString().substringAfter("\"FOLDER\":") val videoUrl = amazonApi.toString().substringAfter("\"FOLDER\":").substringAfter("tempLink\":\"").substringBefore("\"")
.substringAfter("tempLink\":\"").substringBefore("\"")
videoList.add(Video(videoUrl, "Amazon", videoUrl)) videoList.add(Video(videoUrl, "Amazon", videoUrl))
} }
} }
} else if (server.lowercase() == "voex") { if (embedUrl.contains("ok.ru") || embedUrl.contains("okru")) {
VoeExtractor(client).videoFromUrl(url, "Voex")?.let { videoList.add(it) } OkruExtractor(client).videosFromUrl(url).also(videoList::addAll)
} else if (server.lowercase() == "streamlare") { }
videoList.addAll(StreamlareExtractor(client).videosFromUrl(url)) if (embedUrl.contains("filemoon") || embedUrl.contains("moonplayer")) {
} else if (server.lowercase() == "doodstream") { val vidHeaders = headers.newBuilder()
.add("Origin", "https://${url.toHttpUrl().host}")
.add("Referer", "https://${url.toHttpUrl().host}/")
.build()
FilemoonExtractor(client).videosFromUrl(url, prefix = "Filemoon:", headers = vidHeaders).also(videoList::addAll)
}
if (embedUrl.contains("uqload")) {
UqloadExtractor(client).videosFromUrl(url).also(videoList::addAll)
}
if (embedUrl.contains("mp4upload")) {
Mp4uploadExtractor(client).videosFromUrl(url, headers).let { videoList.addAll(it) }
}
if (embedUrl.contains("wishembed") || embedUrl.contains("streamwish") || embedUrl.contains("strwish") || embedUrl.contains("wish")) {
val docHeaders = headers.newBuilder()
.add("Origin", "https://streamwish.to")
.add("Referer", "https://streamwish.to/")
.build()
StreamWishExtractor(client, docHeaders).videosFromUrl(url, videoNameGen = { "StreamWish:$it" }).also(videoList::addAll)
}
if (embedUrl.contains("doodstream") || embedUrl.contains("dood.")) {
val url2 = url.replace("https://doodstream.com/e/", "https://dood.to/e/") val url2 = url.replace("https://doodstream.com/e/", "https://dood.to/e/")
DoodExtractor(client).videoFromUrl(url2, "DoodStream", false)?.let { videoList.add(it) } DoodExtractor(client).videoFromUrl(url2, "DoodStream", false)?.let { videoList.add(it) }
} else if (server.lowercase() == "upload") { }
return YourUploadExtractor(client).videoFromUrl(url, headers = headers) if (embedUrl.contains("streamlare")) {
} else if (server.lowercase().contains("streamwish")) { StreamlareExtractor(client).videosFromUrl(url).let { videoList.addAll(it) }
val docHeaders = headers.newBuilder() }
.add("Referer", "$baseUrl/") if (embedUrl.contains("yourupload") || embedUrl.contains("upload")) {
YourUploadExtractor(client).videoFromUrl(url, headers = headers).let { videoList.addAll(it) }
}
if (embedUrl.contains("burstcloud") || embedUrl.contains("burst")) {
BurstCloudExtractor(client).videoFromUrl(url, headers = headers).let { videoList.addAll(it) }
}
if (embedUrl.contains("fastream")) {
FastreamExtractor(client).videoFromUrl(url).forEach { videoList.add(it) }
}
if (embedUrl.contains("upstream")) {
UpstreamExtractor(client).videosFromUrl(url).let { videoList.addAll(it) }
}
if (embedUrl.contains("streamtape") || embedUrl.contains("stp") || embedUrl.contains("stape")) {
StreamTapeExtractor(client).videoFromUrl(url)?.let { videoList.add(it) }
}
if (embedUrl.contains("ahvsh") || embedUrl.contains("streamhide")) {
StreamHideExtractor(client).videosFromUrl(url, "StreamHide").let { videoList.addAll(it) }
}
if (embedUrl.contains("filelions") || embedUrl.contains("lion")) {
StreamWishExtractor(client, headers).videosFromUrl(url, videoNameGen = { "FileLions:$it" }).also(videoList::addAll)
}
if (embedUrl.contains("tomatomatela")) {
runCatching {
val mainUrl = url.substringBefore("/embed.html#").substringAfter("https://")
val headers = headers.newBuilder()
.set("authority", mainUrl)
.set("accept", "application/json, text/javascript, */*; q=0.01")
.set("accept-language", "es-MX,es-419;q=0.9,es;q=0.8,en;q=0.7")
.set("sec-ch-ua", "\"Chromium\";v=\"106\", \"Google Chrome\";v=\"106\", \"Not;A=Brand\";v=\"99\"")
.set("sec-ch-ua-mobile", "?0")
.set("sec-ch-ua-platform", "Windows")
.set("sec-fetch-dest", "empty")
.set("sec-fetch-mode", "cors")
.set("sec-fetch-site", "same-origin")
.set("x-requested-with", "XMLHttpRequest")
.build() .build()
StreamWishExtractor(client, docHeaders).videosFromUrl(url, "StreamWish") val token = url.substringAfter("/embed.html#")
} else if (server.contains("filemoon") || server.contains("moonplayer")) { val urlRequest = "https://$mainUrl/details.php?v=$token"
FilemoonExtractor(client).videosFromUrl(url, headers = headers).also(videoList::addAll) val response = client.newCall(GET(urlRequest, headers = headers)).execute().asJsoup()
val bodyText = response.select("body").text()
val json = json.decodeFromString<JsonObject>(bodyText)
val status = json["status"]!!.jsonPrimitive!!.content
val file = json["file"]!!.jsonPrimitive!!.content
if (status == "200") { videoList.add(Video(file, "Tomatomatela", file, headers = null)) }
}
} }
} catch (_: Exception) { } } catch (_: Exception) { }
return videoList return videoList
@ -212,25 +278,18 @@ open class Pelisplushd(override val name: String, override val baseUrl: String)
override fun videoFromElement(element: Element) = throw Exception("not used") override fun videoFromElement(element: Element) = throw Exception("not used")
override fun List<Video>.sort(): List<Video> { override fun List<Video>.sort(): List<Video> {
return try { val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
val videoSorted = this.sortedWith( val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
compareBy<Video> { it.quality.replace("[0-9]".toRegex(), "") }.thenByDescending { getNumberFromString(it.quality) }, return this.sortedWith(
).toTypedArray() compareBy(
val userPreferredQuality = preferences.getString("preferred_quality", "Voex") { it.quality.contains(server, true) },
val preferredIdx = videoSorted.indexOfFirst { x -> x.quality == userPreferredQuality } { it.quality.contains(quality) },
if (preferredIdx != -1) { { Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 },
videoSorted.drop(preferredIdx + 1) ),
videoSorted[0] = videoSorted[preferredIdx] ).reversed()
}
videoSorted.toList()
} catch (e: Exception) {
this
}
} }
fun getNumberFromString(epsStr: String): String { fun getNumberFromString(epsStr: String) = epsStr.filter { it.isDigit() }.ifEmpty { "0" }
return epsStr.filter { it.isDigit() }.ifEmpty { "0" }
}
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val filterList = if (filters.isEmpty()) getFilterList() else filters val filterList = if (filters.isEmpty()) getFilterList() else filters
@ -244,23 +303,21 @@ open class Pelisplushd(override val name: String, override val baseUrl: String)
else -> GET("$baseUrl/peliculas?page=$page") else -> GET("$baseUrl/peliculas?page=$page")
} }
} }
override fun searchAnimeFromElement(element: Element): SAnime { override fun searchAnimeFromElement(element: Element) = popularAnimeFromElement(element)
return popularAnimeFromElement(element)
}
override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector() override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector()
override fun searchAnimeSelector(): String = popularAnimeSelector() override fun searchAnimeSelector(): String = popularAnimeSelector()
override fun animeDetailsParse(document: Document): SAnime { override fun animeDetailsParse(document: Document): SAnime {
val anime = SAnime.create() return SAnime.create().apply {
anime.title = document.selectFirst("h1.m-b-5")!!.text() title = document.selectFirst("h1.m-b-5")!!.text()
anime.thumbnail_url = document.selectFirst("div.card-body div.row div.col-sm-3 img.img-fluid")!! thumbnail_url = document.selectFirst("div.card-body div.row div.col-sm-3 img.img-fluid")!!
.attr("src").replace("/w154/", "/w500/") .attr("src").replace("/w154/", "/w500/")
anime.description = document.selectFirst("div.col-sm-4 div.text-large")!!.ownText() description = document.selectFirst("div.col-sm-4 div.text-large")!!.ownText()
anime.genre = document.select("div.p-v-20.p-h-15.text-center a span").joinToString { it.text() } genre = document.select("div.p-v-20.p-h-15.text-center a span").joinToString { it.text() }
anime.status = SAnime.COMPLETED status = SAnime.COMPLETED
return anime }
} }
override fun latestUpdatesNextPageSelector() = throw Exception("not used") override fun latestUpdatesNextPageSelector() = throw Exception("not used")
@ -279,7 +336,7 @@ open class Pelisplushd(override val name: String, override val baseUrl: String)
) )
private class GenreFilter : UriPartFilter( private class GenreFilter : UriPartFilter(
"Tipos", "Géneros",
arrayOf( arrayOf(
Pair("<selecionar>", ""), Pair("<selecionar>", ""),
Pair("Peliculas", "peliculas"), Pair("Peliculas", "peliculas"),
@ -309,22 +366,18 @@ open class Pelisplushd(override val name: String, override val baseUrl: String)
private class Tags(name: String) : AnimeFilter.Text(name) private class Tags(name: String) : AnimeFilter.Text(name)
public open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
AnimeFilter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) { AnimeFilter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second fun toUriPart() = vals[state].second
} }
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
val qualities = arrayOf( ListPreference(screen.context).apply {
"Streamlare:1080p", "Streamlare:720p", "Streamlare:480p", "Streamlare:360p", "Streamlare:240p", // Streamlare key = PREF_SERVER_KEY
"StreamTape", "Amazon", "Voex", "DoodStream", "YourUpload", title = "Preferred server"
) entries = SERVER_LIST
val videoQualityPref = ListPreference(screen.context).apply { entryValues = SERVER_LIST
key = "preferred_quality" setDefaultValue(PREF_SERVER_DEFAULT)
title = "Preferred quality"
entries = qualities
entryValues = qualities
setDefaultValue("Voex")
summary = "%s" summary = "%s"
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
@ -333,7 +386,22 @@ open class Pelisplushd(override val name: String, override val baseUrl: String)
val entry = entryValues[index] as String val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit() preferences.edit().putString(key, entry).commit()
} }
}.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY
title = "Preferred quality"
entries = QUALITY_LIST
entryValues = QUALITY_LIST
setDefaultValue(PREF_QUALITY_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
} }
screen.addPreference(videoQualityPref) }.also(screen::addPreference)
} }
} }

View File

@ -8,11 +8,16 @@ import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.lib.burstcloudextractor.BurstCloudExtractor
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
import eu.kanade.tachiyomi.lib.fastreamextractor.FastreamExtractor
import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
import eu.kanade.tachiyomi.lib.streamlareextractor.StreamlareExtractor import eu.kanade.tachiyomi.lib.streamlareextractor.StreamlareExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
import eu.kanade.tachiyomi.lib.upstreamextractor.UpstreamExtractor
import eu.kanade.tachiyomi.lib.uqloadextractor.UqloadExtractor import eu.kanade.tachiyomi.lib.uqloadextractor.UqloadExtractor
import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor
@ -21,6 +26,7 @@ import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.jsonPrimitive
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
@ -33,6 +39,21 @@ class Pelisplusph(override val name: String, override val baseUrl: String) : Pel
override val supportsLatest = false override val supportsLatest = false
companion object {
private const val PREF_LANGUAGE_KEY = "preferred_language"
private const val PREF_LANGUAGE_DEFAULT = "[LAT]"
private val LANGUAGE_LIST = arrayOf("[LAT]", "[SUB]", "[CAST]")
private const val PREF_SERVER_KEY = "preferred_server"
private const val PREF_SERVER_DEFAULT = "Voe"
private val SERVER_LIST = arrayOf(
"YourUpload", "BurstCloud", "Voe", "Mp4Upload", "Doodstream",
"Upload", "BurstCloud", "Upstream", "StreamTape", "Amazon",
"Fastream", "Filemoon", "StreamWish", "Okru", "Streamlare",
"StreamHide", "Tomatomatela",
)
}
override fun popularAnimeSelector(): String = ".items-peliculas .item-pelicula" override fun popularAnimeSelector(): String = ".items-peliculas .item-pelicula"
override fun popularAnimeNextPageSelector(): String = ".items-peliculas > a" override fun popularAnimeNextPageSelector(): String = ".items-peliculas > a"
@ -73,8 +94,8 @@ class Pelisplusph(override val name: String, override val baseUrl: String) : Pel
val episode = SEpisode.create().apply { val episode = SEpisode.create().apply {
episode_number = 1F episode_number = 1F
name = "PELÍCULA" name = "PELÍCULA"
setUrlWithoutDomain(response.request.url.toString())
} }
episode.setUrlWithoutDomain(response.request.url.toString())
episodes.add(episode) episodes.add(episode)
} else { } else {
var index = 0 var index = 0
@ -84,14 +105,14 @@ class Pelisplusph(override val name: String, override val baseUrl: String) : Pel
}.getOrElse { idxSeas + 1 } }.getOrElse { idxSeas + 1 }
season.select(".item-season-episodes a").reversed().mapIndexed { idx, ep -> season.select(".item-season-episodes a").reversed().mapIndexed { idx, ep ->
index += 1 index += 1
val noEp = runCatching { val noEp = try {
getNumberFromString(ep.ownText()) getNumberFromString(ep.ownText())
}.getOrElse { idx + 1 } } catch (_: Exception) { idx + 1 }
val episode = SEpisode.create().apply {
val episode = SEpisode.create() episode_number = index.toFloat()
episode.episode_number = index.toFloat() name = "T$seasonNumber - E$noEp - ${ep.ownText()}"
episode.name = "T$seasonNumber - E$noEp - ${ep.ownText()}" setUrlWithoutDomain(ep.attr("abs:href"))
episode.setUrlWithoutDomain(baseUrl + ep.attr("href")) }
episodes.add(episode) episodes.add(episode)
} }
} }
@ -115,10 +136,10 @@ class Pelisplusph(override val name: String, override val baseUrl: String) : Pel
document.select("[class*=server-item-]").map { document.select("[class*=server-item-]").map {
val langIdx = getNumberFromString(it.attr("class").substringAfter("server-item-")) val langIdx = getNumberFromString(it.attr("class").substringAfter("server-item-"))
val langItem = document.select("li[data-id=\"$langIdx\"] a").text() val langItem = document.select("li[data-id=\"$langIdx\"] a").text()
val lang = if (langItem.contains("Subtitulado")) "[Sub]" else if (langItem.contains("Latino")) "[Lat]" else "[Cast]" val lang = if (langItem.contains("Subtitulado")) "[SUB]" else if (langItem.contains("Latino")) "[LAT]" else "[CAST]"
it.select("li.tab-video").map { servers -> it.select("li.tab-video").map { servers ->
val url = servers.attr("data-video") val url = servers.attr("data-video")
loadExtractor(url, lang).let { videos -> serverVideoResolver(url, lang).let { videos ->
videoList.addAll(videos) videoList.addAll(videos)
} }
} }
@ -126,9 +147,76 @@ class Pelisplusph(override val name: String, override val baseUrl: String) : Pel
return videoList return videoList
} }
private fun loadExtractor(url: String, prefix: String = ""): List<Video> { private fun serverVideoResolver(url: String, prefix: String = ""): List<Video> {
val videoList = mutableListOf<Video>() val videoList = mutableListOf<Video>()
val embedUrl = url.lowercase() val embedUrl = url.lowercase()
try {
if (embedUrl.contains("voe")) {
VoeExtractor(client).videoFromUrl(url, prefix = "$prefix Voe:")?.let { videoList.add(it) }
}
if ((embedUrl.contains("amazon") || embedUrl.contains("amz")) && !embedUrl.contains("disable")) {
val body = client.newCall(GET(url)).execute().asJsoup()
if (body.select("script:containsData(var shareId)").toString().isNotBlank()) {
val shareId = body.selectFirst("script:containsData(var shareId)")!!.data()
.substringAfter("shareId = \"").substringBefore("\"")
val amazonApiJson = client.newCall(GET("https://www.amazon.com/drive/v1/shares/$shareId?resourceVersion=V2&ContentType=JSON&asset=ALL"))
.execute().asJsoup()
val epId = amazonApiJson.toString().substringAfter("\"id\":\"").substringBefore("\"")
val amazonApi =
client.newCall(GET("https://www.amazon.com/drive/v1/nodes/$epId/children?resourceVersion=V2&ContentType=JSON&limit=200&sort=%5B%22kind+DESC%22%2C+%22modifiedDate+DESC%22%5D&asset=ALL&tempLink=true&shareId=$shareId"))
.execute().asJsoup()
val videoUrl = amazonApi.toString().substringAfter("\"FOLDER\":").substringAfter("tempLink\":\"").substringBefore("\"")
videoList.add(Video(videoUrl, "$prefix Amazon", videoUrl))
}
}
if (embedUrl.contains("ok.ru") || embedUrl.contains("okru")) {
OkruExtractor(client).videosFromUrl(url, prefix).also(videoList::addAll)
}
if (embedUrl.contains("filemoon") || embedUrl.contains("moonplayer")) {
val vidHeaders = headers.newBuilder()
.add("Origin", "https://${url.toHttpUrl().host}")
.add("Referer", "https://${url.toHttpUrl().host}/")
.build()
FilemoonExtractor(client).videosFromUrl(url, prefix = "$prefix Filemoon:", headers = vidHeaders).also(videoList::addAll)
}
if (embedUrl.contains("uqload")) {
UqloadExtractor(client).videosFromUrl(url, prefix = prefix).also(videoList::addAll)
}
if (embedUrl.contains("mp4upload")) {
Mp4uploadExtractor(client).videosFromUrl(url, headers, prefix = prefix).let { videoList.addAll(it) }
}
if (embedUrl.contains("wishembed") || embedUrl.contains("streamwish") || embedUrl.contains("strwish") || embedUrl.contains("wish")) {
val docHeaders = headers.newBuilder()
.add("Origin", "https://streamwish.to")
.add("Referer", "https://streamwish.to/")
.build()
StreamWishExtractor(client, docHeaders).videosFromUrl(url, videoNameGen = { "$prefix StreamWish:$it" }).also(videoList::addAll)
}
if (embedUrl.contains("doodstream") || embedUrl.contains("dood.")) {
val url2 = url.replace("https://doodstream.com/e/", "https://dood.to/e/")
DoodExtractor(client).videoFromUrl(url2, "$prefix DoodStream", false)?.let { videoList.add(it) }
}
if (embedUrl.contains("streamlare")) {
StreamlareExtractor(client).videosFromUrl(url, prefix = prefix).let { videoList.addAll(it) }
}
if (embedUrl.contains("yourupload") || embedUrl.contains("upload")) {
YourUploadExtractor(client).videoFromUrl(url, headers = headers, prefix = prefix).let { videoList.addAll(it) }
}
if (embedUrl.contains("burstcloud") || embedUrl.contains("burst")) {
BurstCloudExtractor(client).videoFromUrl(url, headers = headers, prefix = prefix).let { videoList.addAll(it) }
}
if (embedUrl.contains("fastream")) {
FastreamExtractor(client).videoFromUrl(url, prefix = "$prefix Fastream:").forEach { videoList.add(it) }
}
if (embedUrl.contains("upstream")) {
UpstreamExtractor(client).videosFromUrl(url, prefix = prefix).let { videoList.addAll(it) }
}
if (embedUrl.contains("streamtape") || embedUrl.contains("stp") || embedUrl.contains("stape")) {
StreamTapeExtractor(client).videoFromUrl(url, quality = "$prefix StreamTape")?.let { videoList.add(it) }
}
if (embedUrl.contains("ahvsh") || embedUrl.contains("streamhide")) {
StreamHideExtractor(client).videosFromUrl(url, "$prefix StreamHide").let { videoList.addAll(it) }
}
if (embedUrl.contains("tomatomatela")) { if (embedUrl.contains("tomatomatela")) {
runCatching { runCatching {
val mainUrl = url.substringBefore("/embed.html#").substringAfter("https://") val mainUrl = url.substringBefore("/embed.html#").substringAfter("https://")
@ -154,44 +242,24 @@ class Pelisplusph(override val name: String, override val baseUrl: String) : Pel
if (status == "200") { videoList.add(Video(file, "$prefix Tomatomatela", file, headers = null)) } if (status == "200") { videoList.add(Video(file, "$prefix Tomatomatela", file, headers = null)) }
} }
} }
if (embedUrl.contains("yourupload")) { } catch (_: Exception) { }
val videos = YourUploadExtractor(client).videoFromUrl(url, headers = headers)
videoList.addAll(videos)
}
if (embedUrl.contains("doodstream") || embedUrl.contains("dood.")) {
DoodExtractor(client).videoFromUrl(url, "$prefix DoodStream", false)
?.let { videoList.add(it) }
}
if (embedUrl.contains("okru") || embedUrl.contains("ok.ru")) {
videoList.addAll(
OkruExtractor(client).videosFromUrl(url, prefix, true),
)
}
if (embedUrl.contains("voe")) {
VoeExtractor(client).videoFromUrl(url, "$prefix VoeCDN")?.let { videoList.add(it) }
}
if (embedUrl.contains("filemoon") || embedUrl.contains("moonplayer")) {
FilemoonExtractor(client).videosFromUrl(url, prefix)
.also(videoList::addAll)
}
if (embedUrl.contains("streamlare")) {
videoList.addAll(StreamlareExtractor(client).videosFromUrl(url))
}
if (embedUrl.contains("streamwish")) {
val docHeaders = headers.newBuilder()
.add("Referer", "$baseUrl/")
.build()
StreamWishExtractor(client, docHeaders).videosFromUrl(url, "StreamWish")
}
if (embedUrl.contains("ahvsh") || embedUrl.contains("streamhide")) {
StreamHideExtractor(client).videosFromUrl(url, "StreamHide")
}
if (embedUrl.contains("uqload")) {
UqloadExtractor(client).videosFromUrl(url)
}
return videoList return videoList
} }
override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
val lang = preferences.getString(PREF_LANGUAGE_KEY, PREF_LANGUAGE_DEFAULT)!!
return this.sortedWith(
compareBy(
{ it.quality.contains(lang) },
{ it.quality.contains(server, true) },
{ it.quality.contains(quality) },
{ Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 },
),
).reversed()
}
override fun getFilterList(): AnimeFilterList = AnimeFilterList( override fun getFilterList(): AnimeFilterList = AnimeFilterList(
AnimeFilter.Header("La busqueda por genero ignora los otros filtros"), AnimeFilter.Header("La busqueda por genero ignora los otros filtros"),
GenreFilter(), GenreFilter(),
@ -310,16 +378,12 @@ class Pelisplusph(override val name: String, override val baseUrl: String) : Pel
) )
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
val qualities = arrayOf( ListPreference(screen.context).apply {
"DoodStream", key = PREF_SERVER_KEY
"Voex", title = "Preferred server"
) entries = SERVER_LIST
val videoQualityPref = ListPreference(screen.context).apply { entryValues = SERVER_LIST
key = "preferred_quality" setDefaultValue(PREF_SERVER_DEFAULT)
title = "Preferred quality"
entries = qualities
entryValues = qualities
setDefaultValue("Voex")
summary = "%s" summary = "%s"
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
@ -328,7 +392,38 @@ class Pelisplusph(override val name: String, override val baseUrl: String) : Pel
val entry = entryValues[index] as String val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit() preferences.edit().putString(key, entry).commit()
} }
}.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY
title = "Preferred quality"
entries = QUALITY_LIST
entryValues = QUALITY_LIST
setDefaultValue(PREF_QUALITY_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
} }
screen.addPreference(videoQualityPref) }.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_LANGUAGE_KEY
title = "Preferred language"
entries = LANGUAGE_LIST
entryValues = LANGUAGE_LIST
setDefaultValue(PREF_LANGUAGE_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}.also(screen::addPreference)
} }
} }

View File

@ -9,23 +9,28 @@ import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode import eu.kanade.tachiyomi.animesource.model.SEpisode
import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.lib.burstcloudextractor.BurstCloudExtractor
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
import eu.kanade.tachiyomi.lib.fastreamextractor.FastreamExtractor
import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor
import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
import eu.kanade.tachiyomi.lib.streamlareextractor.StreamlareExtractor import eu.kanade.tachiyomi.lib.streamlareextractor.StreamlareExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
import eu.kanade.tachiyomi.lib.upstreamextractor.UpstreamExtractor
import eu.kanade.tachiyomi.lib.uqloadextractor.UqloadExtractor import eu.kanade.tachiyomi.lib.uqloadextractor.UqloadExtractor
import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor import eu.kanade.tachiyomi.lib.voeextractor.VoeExtractor
import eu.kanade.tachiyomi.lib.vudeoextractor.VudeoExtractor import eu.kanade.tachiyomi.lib.vudeoextractor.VudeoExtractor
import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.jsonPrimitive
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
@ -38,6 +43,16 @@ class Pelisplusto(override val name: String, override val baseUrl: String) : Pel
override val supportsLatest = false override val supportsLatest = false
companion object {
private const val PREF_SERVER_KEY = "preferred_server"
private const val PREF_SERVER_DEFAULT = "Voe"
private val SERVER_LIST = arrayOf(
"YourUpload", "BurstCloud", "Voe", "Mp4Upload", "Doodstream",
"Upload", "BurstCloud", "Upstream", "StreamTape", "Amazon",
"Fastream", "Filemoon", "StreamWish", "Okru", "Streamlare",
)
}
override fun popularAnimeSelector(): String = "article.item" override fun popularAnimeSelector(): String = "article.item"
override fun popularAnimeNextPageSelector(): String = "a[rel=\"next\"]" override fun popularAnimeNextPageSelector(): String = "a[rel=\"next\"]"
@ -46,7 +61,7 @@ class Pelisplusto(override val name: String, override val baseUrl: String) : Pel
override fun popularAnimeFromElement(element: Element): SAnime { override fun popularAnimeFromElement(element: Element): SAnime {
val anime = SAnime.create() val anime = SAnime.create()
anime.setUrlWithoutDomain(element.select("a").attr("href")) anime.setUrlWithoutDomain(element.select("a").attr("abs:href"))
anime.title = element.select("a h2").text() anime.title = element.select("a h2").text()
anime.thumbnail_url = element.select("a .item__image picture img").attr("data-src") anime.thumbnail_url = element.select("a .item__image picture img").attr("data-src")
return anime return anime
@ -57,6 +72,7 @@ class Pelisplusto(override val name: String, override val baseUrl: String) : Pel
anime.title = document.selectFirst(".home__slider_content div h1.slugh1")!!.text() anime.title = document.selectFirst(".home__slider_content div h1.slugh1")!!.text()
anime.description = document.selectFirst(".home__slider_content .description")!!.text() anime.description = document.selectFirst(".home__slider_content .description")!!.text()
anime.genre = document.select(".home__slider_content div:nth-child(5) > a").joinToString { it.text() } anime.genre = document.select(".home__slider_content div:nth-child(5) > a").joinToString { it.text() }
anime.artist = document.selectFirst(".home__slider_content div:nth-child(7) > a")?.text()
anime.status = SAnime.COMPLETED anime.status = SAnime.COMPLETED
return anime return anime
} }
@ -68,8 +84,8 @@ class Pelisplusto(override val name: String, override val baseUrl: String) : Pel
val episode = SEpisode.create().apply { val episode = SEpisode.create().apply {
episode_number = 1F episode_number = 1F
name = "PELÍCULA" name = "PELÍCULA"
setUrlWithoutDomain(response.request.url.toString())
} }
episode.setUrlWithoutDomain(response.request.url.toString())
episodes.add(episode) episodes.add(episode)
} else { } else {
var jsonscript = "" var jsonscript = ""
@ -88,10 +104,11 @@ class Pelisplusto(override val name: String, override val baseUrl: String) : Pel
val season = jsonElement["season"]!!.jsonPrimitive!!.content val season = jsonElement["season"]!!.jsonPrimitive!!.content
val title = jsonElement["title"]!!.jsonPrimitive!!.content val title = jsonElement["title"]!!.jsonPrimitive!!.content
val ep = jsonElement["episode"]!!.jsonPrimitive!!.content val ep = jsonElement["episode"]!!.jsonPrimitive!!.content
val episode = SEpisode.create() val episode = SEpisode.create().apply {
episode.episode_number = index.toFloat() episode_number = index.toFloat()
episode.name = "T$season - E$ep - $title" name = "T$season - E$ep - $title"
episode.setUrlWithoutDomain("${response.request.url}/season/$season/episode/$ep") setUrlWithoutDomain("${response.request.url}/season/$season/episode/$ep")
}
episodes.add(episode) episodes.add(episode)
} }
} }
@ -126,7 +143,7 @@ class Pelisplusto(override val name: String, override val baseUrl: String) : Pel
fetchUrls(script).map { fetchUrls(script).map {
val link = it.replace("https://sblanh.com", "https://lvturbo.com") val link = it.replace("https://sblanh.com", "https://lvturbo.com")
.replace(Regex("([a-zA-Z0-9]{0,8}[a-zA-Z0-9_-]+)=https:\\/\\/ww3.pelisplus.to.*"), "") .replace(Regex("([a-zA-Z0-9]{0,8}[a-zA-Z0-9_-]+)=https:\\/\\/ww3.pelisplus.to.*"), "")
loadExtractor(link).let { videos -> serverVideoResolver(link).let { videos ->
videoList.addAll(videos) videoList.addAll(videos)
} }
} }
@ -135,7 +152,7 @@ class Pelisplusto(override val name: String, override val baseUrl: String) : Pel
val link = url.replace("https://sblanh.com", "https://lvturbo.com") val link = url.replace("https://sblanh.com", "https://lvturbo.com")
.replace(Regex("([a-zA-Z0-9]{0,8}[a-zA-Z0-9_-]+)=https:\\/\\/ww3.pelisplus.to.*"), "") .replace(Regex("([a-zA-Z0-9]{0,8}[a-zA-Z0-9_-]+)=https:\\/\\/ww3.pelisplus.to.*"), "")
loadExtractor(link).let { videos -> serverVideoResolver(link).let { videos ->
videoList.addAll(videos) videoList.addAll(videos)
} }
} }
@ -143,11 +160,93 @@ class Pelisplusto(override val name: String, override val baseUrl: String) : Pel
return videoList return videoList
} }
private fun loadExtractor(url: String, prefix: String = ""): List<Video> { override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
val server = preferences.getString(PREF_SERVER_KEY, PREF_SERVER_DEFAULT)!!
return this.sortedWith(
compareBy(
{ it.quality.contains(server, true) },
{ it.quality.contains(quality) },
{ Regex("""(\d+)p""").find(it.quality)?.groupValues?.get(1)?.toIntOrNull() ?: 0 },
),
).reversed()
}
private fun serverVideoResolver(url: String): List<Video> {
val videoList = mutableListOf<Video>() val videoList = mutableListOf<Video>()
val embedUrl = url.lowercase() val embedUrl = url.lowercase()
if (embedUrl.contains("tomatomatela")) {
try { try {
if (embedUrl.contains("voe")) {
VoeExtractor(client).videoFromUrl(url, prefix = "Voe:")?.let { videoList.add(it) }
}
if ((embedUrl.contains("amazon") || embedUrl.contains("amz")) && !embedUrl.contains("disable")) {
val body = client.newCall(GET(url)).execute().asJsoup()
if (body.select("script:containsData(var shareId)").toString().isNotBlank()) {
val shareId = body.selectFirst("script:containsData(var shareId)")!!.data()
.substringAfter("shareId = \"").substringBefore("\"")
val amazonApiJson = client.newCall(GET("https://www.amazon.com/drive/v1/shares/$shareId?resourceVersion=V2&ContentType=JSON&asset=ALL"))
.execute().asJsoup()
val epId = amazonApiJson.toString().substringAfter("\"id\":\"").substringBefore("\"")
val amazonApi =
client.newCall(GET("https://www.amazon.com/drive/v1/nodes/$epId/children?resourceVersion=V2&ContentType=JSON&limit=200&sort=%5B%22kind+DESC%22%2C+%22modifiedDate+DESC%22%5D&asset=ALL&tempLink=true&shareId=$shareId"))
.execute().asJsoup()
val videoUrl = amazonApi.toString().substringAfter("\"FOLDER\":").substringAfter("tempLink\":\"").substringBefore("\"")
videoList.add(Video(videoUrl, "Amazon", videoUrl))
}
}
if (embedUrl.contains("ok.ru") || embedUrl.contains("okru")) {
OkruExtractor(client).videosFromUrl(url).also(videoList::addAll)
}
if (embedUrl.contains("filemoon") || embedUrl.contains("moonplayer")) {
val vidHeaders = headers.newBuilder()
.add("Origin", "https://${url.toHttpUrl().host}")
.add("Referer", "https://${url.toHttpUrl().host}/")
.build()
FilemoonExtractor(client).videosFromUrl(url, prefix = "Filemoon:", headers = vidHeaders).also(videoList::addAll)
}
if (embedUrl.contains("uqload")) {
UqloadExtractor(client).videosFromUrl(url).also(videoList::addAll)
}
if (embedUrl.contains("mp4upload")) {
Mp4uploadExtractor(client).videosFromUrl(url, headers).let { videoList.addAll(it) }
}
if (embedUrl.contains("wishembed") || embedUrl.contains("streamwish") || embedUrl.contains("strwish") || embedUrl.contains("wish")) {
val docHeaders = headers.newBuilder()
.add("Origin", "https://streamwish.to")
.add("Referer", "https://streamwish.to/")
.build()
StreamWishExtractor(client, docHeaders).videosFromUrl(url, videoNameGen = { "StreamWish:$it" }).also(videoList::addAll)
}
if (embedUrl.contains("doodstream") || embedUrl.contains("dood.")) {
val url2 = url.replace("https://doodstream.com/e/", "https://dood.to/e/")
DoodExtractor(client).videoFromUrl(url2, "DoodStream", false)?.let { videoList.add(it) }
}
if (embedUrl.contains("streamlare")) {
StreamlareExtractor(client).videosFromUrl(url).let { videoList.addAll(it) }
}
if (embedUrl.contains("yourupload") || embedUrl.contains("upload")) {
YourUploadExtractor(client).videoFromUrl(url, headers = headers).let { videoList.addAll(it) }
}
if (embedUrl.contains("burstcloud") || embedUrl.contains("burst")) {
BurstCloudExtractor(client).videoFromUrl(url, headers = headers).let { videoList.addAll(it) }
}
if (embedUrl.contains("fastream")) {
FastreamExtractor(client).videoFromUrl(url).forEach { videoList.add(it) }
}
if (embedUrl.contains("upstream")) {
UpstreamExtractor(client).videosFromUrl(url).let { videoList.addAll(it) }
}
if (embedUrl.contains("streamtape") || embedUrl.contains("stp") || embedUrl.contains("stape")) {
StreamTapeExtractor(client).videoFromUrl(url)?.let { videoList.add(it) }
}
if (embedUrl.contains("ahvsh") || embedUrl.contains("streamhide")) {
StreamHideExtractor(client).videosFromUrl(url, "StreamHide").let { videoList.addAll(it) }
}
if (embedUrl.contains("vudeo") || embedUrl.contains("vudea")) {
VudeoExtractor(client).videosFromUrl(url).let { videoList.addAll(it) }
}
if (embedUrl.contains("tomatomatela")) {
runCatching {
val mainUrl = url.substringBefore("/embed.html#").substringAfter("https://") val mainUrl = url.substringBefore("/embed.html#").substringAfter("https://")
val headers = headers.newBuilder() val headers = headers.newBuilder()
.set("authority", mainUrl) .set("authority", mainUrl)
@ -168,48 +267,10 @@ class Pelisplusto(override val name: String, override val baseUrl: String) : Pel
val json = json.decodeFromString<JsonObject>(bodyText) val json = json.decodeFromString<JsonObject>(bodyText)
val status = json["status"]!!.jsonPrimitive!!.content val status = json["status"]!!.jsonPrimitive!!.content
val file = json["file"]!!.jsonPrimitive!!.content val file = json["file"]!!.jsonPrimitive!!.content
if (status == "200") { videoList.add(Video(file, "$prefix Tomatomatela", file, headers = null)) } if (status == "200") { videoList.add(Video(file, "Tomatomatela", file, headers = null)) }
}
}
} catch (_: Exception) { } } catch (_: Exception) { }
}
if (embedUrl.contains("yourupload")) {
val videos = YourUploadExtractor(client).videoFromUrl(url, headers = headers)
videoList.addAll(videos)
}
if (embedUrl.contains("doodstream") || embedUrl.contains("dood.")) {
DoodExtractor(client).videoFromUrl(url, "$prefix DoodStream", false)
?.let { videoList.add(it) }
}
if (embedUrl.contains("okru") || embedUrl.contains("ok.ru")) {
videoList.addAll(
OkruExtractor(client).videosFromUrl(url, prefix, true),
)
}
if (embedUrl.contains("voe")) {
VoeExtractor(client).videoFromUrl(url)?.let { videoList.add(it) }
}
if (embedUrl.contains("filemoon") || embedUrl.contains("moonplayer")) {
FilemoonExtractor(client).videosFromUrl(url, prefix)
.also(videoList::addAll)
}
if (embedUrl.contains("streamlare")) {
videoList.addAll(StreamlareExtractor(client).videosFromUrl(url))
}
if (embedUrl.contains("uqload")) {
UqloadExtractor(client).videosFromUrl(url)
}
if (embedUrl.contains("streamwish")) {
val docHeaders = headers.newBuilder()
.add("Referer", "$baseUrl/")
.build()
StreamWishExtractor(client, docHeaders).videosFromUrl(url, "StreamWish")
}
if (embedUrl.contains("ahvsh") || embedUrl.contains("streamhide")) {
StreamHideExtractor(client).videosFromUrl(url, "StreamHide")
}
if (embedUrl.contains("vudeo")) {
VudeoExtractor(client).videosFromUrl(url)
}
return videoList return videoList
} }
@ -259,16 +320,12 @@ class Pelisplusto(override val name: String, override val baseUrl: String) : Pel
) )
override fun setupPreferenceScreen(screen: PreferenceScreen) { override fun setupPreferenceScreen(screen: PreferenceScreen) {
val qualities = arrayOf( ListPreference(screen.context).apply {
"Voex", key = PREF_SERVER_KEY
"DoodStream", title = "Preferred server"
) entries = SERVER_LIST
val videoQualityPref = ListPreference(screen.context).apply { entryValues = SERVER_LIST
key = "preferred_quality" setDefaultValue(PREF_SERVER_DEFAULT)
title = "Preferred quality"
entries = qualities
entryValues = qualities
setDefaultValue("Voex")
summary = "%s" summary = "%s"
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
@ -277,7 +334,22 @@ class Pelisplusto(override val name: String, override val baseUrl: String) : Pel
val entry = entryValues[index] as String val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit() preferences.edit().putString(key, entry).commit()
} }
}.also(screen::addPreference)
ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY
title = "Preferred quality"
entries = QUALITY_LIST
entryValues = QUALITY_LIST
setDefaultValue(PREF_QUALITY_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
} }
screen.addPreference(videoQualityPref) }.also(screen::addPreference)
} }
} }