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
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 script = document.selectFirst("script:containsData(const sources),script:containsData(var sources)")
?.data()
?: return null
val videoUrl = script.substringAfter("hls': '").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)
}
}

View File

@ -5,7 +5,7 @@ ext {
extName = 'Animefenix'
pkgNameSuffix = 'es.animefenix'
extClass = '.Animefenix'
extVersionCode = 29
extVersionCode = 30
libVersion = '13'
}
@ -13,11 +13,17 @@ dependencies {
implementation(project(':lib-mp4upload-extractor'))
implementation(project(':lib-streamtape-extractor'))
implementation(project(':lib-yourupload-extractor'))
implementation(project(':lib-uqload-extractor'))
implementation(project(':lib-okru-extractor'))
implementation(project(':lib-burstcloud-extractor'))
implementation(project(':lib-streamwish-extractor'))
implementation(project(':lib-filemoon-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.model.AnimeFilter
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.SEpisode
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.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.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.voeextractor.VoeExtractor
import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.net.URLDecoder
class Animefenix : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
class Animefenix : ConfigurableAnimeSource, AnimeHttpSource() {
override val name = "AnimeFenix"
@ -42,117 +48,43 @@ class Animefenix : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val client: OkHttpClient = network.cloudflareClient
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
private val preferences: SharedPreferences by lazy { 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 popularAnimeFromElement(element: Element): SAnime {
val anime = SAnime.create().apply {
setUrlWithoutDomain(
element.select("figure.image a").attr("href"),
)
override fun popularAnimeParse(response: Response): AnimesPage {
val document = response.asJsoup()
val elements = document.select("article.serie-card")
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()
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()
}
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> {
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 latestUpdatesParse(response: Response) = popularAnimeParse(response)
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
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 {
val anime = SAnime.create().apply {
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 {
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()
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())
}
return anime
}
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 {
val document = client.newCall(GET(url)).execute().asJsoup()
val videoURl = document.selectFirst("script:containsData(sources: [)")!!.data()
@ -340,16 +382,12 @@ class Animefenix : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
val qualities = arrayOf(
"Okru:1080p", "Okru:720p", "Okru:480p", "Okru:360p", "Okru:240p", "Okru:144p", // Okru
"Amazon", "AmazonES", "StreamTape", "Fireload", "Mp4upload", "YourUpload", "StreamWish",
)
val videoQualityPref = ListPreference(screen.context).apply {
key = "preferred_quality"
ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY
title = "Preferred quality"
entries = qualities
entryValues = qualities
setDefaultValue("Amazon")
entries = QUALITY_LIST
entryValues = QUALITY_LIST
setDefaultValue(PREF_QUALITY_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
@ -358,8 +396,22 @@ class Animefenix : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val entry = entryValues[index] as String
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: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext {
extName = 'AnimeFLV'
pkgNameSuffix = 'es.animeflv'
extClass = '.AnimeFlv'
extVersionCode = 50
extVersionCode = 51
libVersion = '13'
}

View File

@ -41,7 +41,7 @@ class AnimeFlv : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val lang = "es"
override val supportsLatest = false
override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient
@ -51,24 +51,30 @@ class AnimeFlv : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
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 popularAnimeRequest(page: Int): Request = GET("$baseUrl/browse?order=rating&page=$page")
override fun popularAnimeFromElement(element: Element): SAnime {
val anime = SAnime.create()
anime.setUrlWithoutDomain(
baseUrl + element.select("div.Description a.Button")
.attr("href"),
)
anime.setUrlWithoutDomain(element.select("div.Description a.Button").attr("abs:href"))
anime.title = element.select("a h3").text()
anime.thumbnail_url = try {
element.select("a div.Image figure img").attr("src")
} catch (e: Exception) {
element.select("a div.Image figure img").attr("data-cfsrc")
}
anime.description =
element.select("div.Description p:eq(2)").text().removeSurrounding("\"")
anime.description = element.select("div.Description p:eq(2)").text().removeSurrounding("\"")
return anime
}
@ -127,7 +133,7 @@ class AnimeFlv : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val docHeaders = headers.newBuilder()
.add("Referer", "$baseUrl/")
.build()
StreamWishExtractor(client, docHeaders).videosFromUrl(url, "StreamWish")
StreamWishExtractor(client, docHeaders).videosFromUrl(url, videoNameGen = { "StreamWish:$it" })
}
else -> null
}
@ -145,27 +151,6 @@ class AnimeFlv : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
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 {
val filterList = if (filters.isEmpty()) getFilterList() else filters
val genreFilter = filterList.find { it is GenreFilter } as GenreFilter
@ -278,9 +263,7 @@ class AnimeFlv : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
fun toUriPart() = vals[state].second
}
override fun searchAnimeFromElement(element: Element): SAnime {
return popularAnimeFromElement(element)
}
override fun searchAnimeFromElement(element: Element) = popularAnimeFromElement(element)
override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector()
@ -288,7 +271,7 @@ class AnimeFlv : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun animeDetailsParse(document: Document): SAnime {
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.description = document.selectFirst("div.Description")!!.text().removeSurrounding("\"")
anime.genre = document.select("nav.Nvgnrs a").joinToString { it.text() }
@ -296,10 +279,6 @@ class AnimeFlv : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
return anime
}
private fun externalOrInternalImg(url: String): String {
return if (url.contains("https")) url else "$baseUrl/$url"
}
private fun parseStatus(statusString: String): Int {
return when {
statusString.contains("En emision") -> SAnime.ONGOING
@ -316,19 +295,25 @@ class AnimeFlv : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
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) {
val videoQualityPref = ListPreference(screen.context).apply {
key = "preferred_quality"
title = "Preferred quality"
entries = arrayOf(
"Okru:1080p", "Okru:720p", "Okru:480p", "Okru:360p", "Okru:240p", "Okru:144p", // Okru
"YourUpload", "DoodStream", "StreamTape",
) // 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")
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 ->
@ -337,7 +322,22 @@ class AnimeFlv : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val entry = entryValues[index] as String
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'
pkgNameSuffix = 'es.animeid'
extClass = '.AnimeID'
extVersionCode = 6
extVersionCode = 7
libVersion = '13'
}

View File

@ -28,7 +28,6 @@ import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.net.URI
import java.text.SimpleDateFormat
import java.util.Date
@ -52,7 +51,7 @@ class AnimeID : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
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 {
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 episodeListSelector() = "ul.ListCaps li a"
override fun episodeListSelector() = throw Exception("not used")
override fun episodeListParse(response: Response): List<SEpisode> {
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)
}
@ -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")
.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>")
val jObject = json.decodeFromString<JsonObject>(responseString)
var listCaps = jObject["list"]!!.jsonArray
val listCaps = jObject["list"]!!.jsonArray
listCaps!!.forEach { cap ->
var capParsed = cap.jsonObject
val capParsed = cap.jsonObject
val epNum = capParsed["numero"]!!.jsonPrimitive.content!!.toFloat()
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.name = "Episodio $epNum"
dateUpload!!.also { episode.date_upload = it }
@ -126,28 +124,17 @@ class AnimeID : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val document = response.asJsoup()
val videoList = mutableListOf<Video>()
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 url = jsonUnescape!!.substringAfter("src=\"").substringBefore("\"").replace("\\\\", "\\")
val quality = getDomainName(url)
if (quality == "streamtape") {
val video = StreamTapeExtractor(client).videoFromUrl(url, "Streamtape")
if (video != null) {
videoList.add(video)
}
val url = jsonUnescape.substringAfter("src=\"").substringBefore("\"").replace("\\\\", "\\")
if (url.contains("streamtape")) {
StreamTapeExtractor(client).videoFromUrl(url)?.let { videoList.add(it) }
}
}
return videoList
}
private fun getDomainName(url: 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? {
private fun unescapeJava(escaped: String): String {
var escaped = escaped
if (escaped.indexOf("\\u") == -1) return escaped
var processed = ""
@ -165,9 +152,7 @@ class AnimeID : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun animeDetailsParse(document: Document): SAnime {
val anime = SAnime.create()
anime.thumbnail_url = externalOrInternalImg(
document.selectFirst("#anime figure img.cover")!!.attr("src"),
)
anime.thumbnail_url = document.selectFirst("#anime figure img.cover")!!.attr("abs:src")
anime.title = document.selectFirst("#anime section hgroup h1")!!.text()
anime.description = document.selectFirst("#anime section p.sinopsis")!!.text().removeSurrounding("\"")
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 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 {
val filterList = if (filters.isEmpty()) getFilterList() else filters
val genreFilter = filterList.find { it is GenreFilter } as GenreFilter
val orderByFilter = filters.find { it is OrderByFilter } as OrderByFilter
var request = when {
query.isNotBlank() -> GET("$baseUrl/buscar?q=$query&pag=$page&sort=newest")
genreFilter.state != 0 -> GET("$baseUrl/genero/${genreFilter.toUriPart()}?pag=$page&sort=newest")
else -> GET("$baseUrl/series?sort=newest&pag=$page")
return when {
query.isNotBlank() -> GET("$baseUrl/buscar?q=$query&pag=$page&sort=${orderByFilter.toUriPart()}")
genreFilter.state != 0 -> GET("$baseUrl/genero/${genreFilter.toUriPart()}?pag=$page&sort=${orderByFilter.toUriPart()}")
orderByFilter.state != 0 -> GET("$baseUrl/series?sort=${orderByFilter.toUriPart()}&pag=$page")
else -> popularAnimeRequest(page)
}
return request
}
override fun searchAnimeFromElement(element: Element): SAnime = popularAnimeFromElement(element)
@ -223,10 +188,11 @@ class AnimeID : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
AnimeFilter.Header("La busqueda por texto ignora el filtro"),
GenreFilter(),
OrderByFilter(),
)
private class GenreFilter : UriPartFilter(
"Generos",
"Géneros",
arrayOf(
Pair("<Selecionar>", ""),
Pair("+18", "18"),
@ -356,9 +322,15 @@ class AnimeID : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
fun toUriPart() = vals[state].second
}
private fun externalOrInternalImg(url: String): String {
return if (url.contains("https")) url else "$baseUrl/$url"
}
private class OrderByFilter : UriPartFilter(
"Ordenar Por",
arrayOf(
Pair("<Seleccionar>", ""),
Pair("Recientes", "newest"),
Pair("A-Z", "asc"),
Pair("Más vistos", "views"),
),
)
private fun parseStatus(statusString: String): Int {
return when {
@ -372,17 +344,17 @@ class AnimeID : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
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 setupPreferenceScreen(screen: PreferenceScreen) {
val videoQualityPref = ListPreference(screen.context).apply {
ListPreference(screen.context).apply {
key = "preferred_quality"
title = "Preferred quality"
entries = arrayOf("Stape", "Streamtape", "hd", "sd", "low", "lowest", "mobile")
entryValues = arrayOf("Stape", "Streamtape", "hd", "sd", "low", "lowest", "mobile")
setDefaultValue("Streamtape")
title = "Preferred server"
entries = arrayOf("StreamTape")
entryValues = arrayOf("StreamTape")
setDefaultValue("StreamTape")
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
@ -391,7 +363,6 @@ class AnimeID : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
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 = 'AnimeLatinoHD'
pkgNameSuffix = 'es.animelatinohd'
extClass = '.AnimeLatinoHD'
extVersionCode = 28
extVersionCode = 29
libVersion = '13'
}
@ -14,6 +14,7 @@ dependencies {
implementation(project(':lib-streamtape-extractor'))
implementation(project(':lib-okru-extractor'))
implementation(project(':lib-dood-extractor'))
implementation(project(':lib-streamwish-extractor'))
}
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.SEpisode
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.filemoonextractor.FilemoonExtractor
import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor
import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor
import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement
@ -27,16 +27,15 @@ import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
class AnimeLatinoHD : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
class AnimeLatinoHD : ConfigurableAnimeSource, AnimeHttpSource() {
override val name = "AnimeLatinoHD"
@ -54,13 +53,31 @@ class AnimeLatinoHD : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
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 popularAnimeParse(response: Response): AnimesPage {
val document = response.asJsoup()
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()
document.select("script").forEach { script ->
if (script.data().contains("{\"props\":{\"pageProps\":")) {
@ -68,6 +85,17 @@ class AnimeLatinoHD : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val props = jObject["props"]!!.jsonObject
val pageProps = props["pageProps"]!!.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
popularToday.forEach { item ->
val animeItem = item!!.jsonObject
@ -79,12 +107,13 @@ class AnimeLatinoHD : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
}
}
}
}
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 {
val document = response.asJsoup()
@ -107,8 +136,6 @@ class AnimeLatinoHD : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
return newAnime
}
override fun animeDetailsParse(document: Document) = throw Exception("not used")
override fun episodeListParse(response: Response): List<SEpisode> {
val document = response.asJsoup()
val episodeList = mutableListOf<SEpisode>()
@ -132,12 +159,8 @@ class AnimeLatinoHD : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
return episodeList
}
override fun episodeListSelector() = "uwu"
override fun episodeFromElement(element: Element) = throw Exception("not used")
private fun parseJsonArray(json: JsonElement?): List<JsonElement> {
var list = mutableListOf<JsonElement>()
val list = mutableListOf<JsonElement>()
json!!.jsonObject!!.entries!!.forEach { list.add(it.value) }
return list
}
@ -175,35 +198,29 @@ class AnimeLatinoHD : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
).execute()
val locationsDdh = request!!.networkResponse.toString()
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()
if (embedUrl.contains("filemoon")) {
FilemoonExtractor(client).videosFromUrl(url, language)
.also(videoList::addAll)
val vidHeaders = headers.newBuilder()
.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")) {
val video = StreamTapeExtractor(client).videoFromUrl(url, language + "Streamtape")
if (video != null) {
videoList.add(video)
}
StreamTapeExtractor(client).videoFromUrl(url, "$language Streamtape")?.let { videoList.add(it) }
}
if (embedUrl.contains("dood")) {
val video = try {
DoodExtractor(client).videoFromUrl(url, language + "DoodStream")
} catch (e: Exception) {
null
}
if (video != null) {
videoList.add(video)
}
DoodExtractor(client).videoFromUrl(url, "$language DoodStream")?.let { videoList.add(it) }
}
if (embedUrl.contains("okru") || embedUrl.contains("ok.ru")) {
val videos = OkruExtractor(client).videosFromUrl(url, language)
videoList.addAll(videos)
OkruExtractor(client).videosFromUrl(url, language).also(videoList::addAll)
}
if (embedUrl.contains("solidfiles")) {
val videos = SolidFilesExtractor(client).videosFromUrl(url, language)
videoList.addAll(videos)
SolidFilesExtractor(client).videosFromUrl(url, language).also(videoList::addAll)
}
if (embedUrl.contains("od.lk")) {
videoList.add(Video(url, language + "Od.lk", url))
@ -219,31 +236,18 @@ class AnimeLatinoHD : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
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> {
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", "[Sub] 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" }
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 searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
@ -268,10 +272,6 @@ class AnimeLatinoHD : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
TypeFilter(),
)
override fun searchAnimeFromElement(element: Element): SAnime {
return popularAnimeFromElement(element)
}
override fun searchAnimeParse(response: Response): AnimesPage {
val document = response.asJsoup()
val animeList = mutableListOf<SAnime>()
@ -296,10 +296,6 @@ class AnimeLatinoHD : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
return AnimesPage(animeList, hasNextPage)
}
override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector()
override fun searchAnimeSelector(): String = popularAnimeSelector()
private class GenreFilter : UriPartFilter(
"Géneros",
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) {
val options = arrayOf(
"[Sub] Okru:1080p", "[Sub] Okru:720p", "[Sub] Okru:480p", "[Sub] Okru:360p", "[Sub] Okru:240p", // Okru [Sub]
"[Lat] Okru:1080p", "[Lat] Okru:720p", "[Lat] Okru:480p", "[Lat] Okru:360p", "[Lat] Okru:240p", // Okru [Lat]
"[Sub] StreamTape", "[Lat] StreamTape", // video servers without resolution
"[Sub] DoodStream", "[Lat] DoodStream", // video servers without resolution
"[Sub] SolidFiles", "[Lat] SolidFiles", // video servers without resolution
"[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")
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 ->
@ -424,7 +401,38 @@ class AnimeLatinoHD : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val entry = entryValues[index] as String
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'
pkgNameSuffix = 'es.animemovil'
extClass = '.AnimeMovil'
extVersionCode = 3
extVersionCode = 4
libVersion = '13'
}

View File

@ -58,6 +58,21 @@ class AnimeMovil : ConfigurableAnimeSource, AnimeHttpSource() {
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 popularAnimeParse(response: Response): AnimesPage {
@ -203,7 +218,7 @@ class AnimeMovil : ConfigurableAnimeSource, AnimeHttpSource() {
videoList.add(Video(fileSrc, "$serverName:HLS", fileSrc, headers = null))
}
if (fileSrc.contains(".mp4")) {
videoList.add(Video(fileSrc, serverName, fileSrc, headers = null))
videoList.add(Video(fileSrc, "$serverName:MP4", fileSrc, headers = null))
}
}
} else {
@ -222,65 +237,62 @@ class AnimeMovil : ConfigurableAnimeSource, AnimeHttpSource() {
val embedUrl = url.lowercase()
try {
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")) {
FilemoonExtractor(client).videosFromUrl(url).also(videoList::addAll)
FilemoonExtractor(client).videosFromUrl(url, prefix = "Filemoon:").also(videoList::addAll)
}
if (embedUrl.contains("uqload")) {
UqloadExtractor(client).videosFromUrl(url)
UqloadExtractor(client).videosFromUrl(url).also(videoList::addAll)
}
if (embedUrl.contains("mp4upload")) {
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")) {
val docHeaders = headers.newBuilder()
.add("Referer", "$baseUrl/")
.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.")) {
DoodExtractor(client).videoFromUrl(url, "DoodStream", false)?.let { videoList.add(it) }
}
if (embedUrl.contains("streamlare")) {
videoList.addAll(StreamlareExtractor(client).videosFromUrl(url))
StreamlareExtractor(client).videosFromUrl(url).also(videoList::addAll)
}
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")) {
BurstCloudExtractor(client).videoFromUrl(url, headers = headers).let { videoList.addAll(it) }
BurstCloudExtractor(client).videoFromUrl(url, headers = headers).also(videoList::addAll)
}
if (embedUrl.contains("fastream")) {
FastreamExtractor(client).videoFromUrl(url).forEach { videoList.add(it) }
FastreamExtractor(client).videoFromUrl(url).also(videoList::addAll)
}
if (embedUrl.contains("upstream")) {
UpstreamExtractor(client).videosFromUrl(url).let { videoList.addAll(it) }
UpstreamExtractor(client).videosFromUrl(url).also(videoList::addAll)
}
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) {}
return videoList
}
override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString("preferred_quality", "Voe")
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
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 getFilterList(): AnimeFilterList = AnimeFilterList(
@ -396,19 +408,12 @@ class AnimeMovil : ConfigurableAnimeSource, AnimeHttpSource() {
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
val qualities = arrayOf(
"PlusTube", "PlusVid", "PlusIm", "PlusWish", "PlusHub", "PlusDex",
"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"
ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY
title = "Preferred quality"
entries = qualities
entryValues = qualities
setDefaultValue("Voe")
entries = QUALITY_LIST
entryValues = QUALITY_LIST
setDefaultValue(PREF_QUALITY_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
@ -417,8 +422,23 @@ class AnimeMovil : ConfigurableAnimeSource, AnimeHttpSource() {
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()
}
screen.addPreference(videoQualityPref)
}.also(screen::addPreference)
}
@Serializable

View File

@ -5,7 +5,7 @@ ext {
extName = 'Animeyt'
pkgNameSuffix = 'es.animeyt'
extClass = '.Animeyt'
extVersionCode = 7
extVersionCode = 8
libVersion = '13'
}
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.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.json.Json
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
@ -21,12 +20,11 @@ import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.lang.Exception
class Animeyt : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val name = "Animeyt"
override val name = "AnimeYT"
override val baseUrl = "https://ytanime.tv"
@ -36,15 +34,23 @@ class Animeyt : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val client: OkHttpClient = network.cloudflareClient
private val json: Json by injectLazy()
private val preferences: SharedPreferences by lazy {
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 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 {
val anime = SAnime.create()
@ -83,8 +89,13 @@ class Animeyt : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
.split(".")[0]
.replace("https://", "")
.replace("http://", "")
val url = container.attr("src")
var url = container.attr("src")
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) }
}
}
@ -98,24 +109,15 @@ class Animeyt : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
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", "Fastream: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" }
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 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 latestUpdatesRequest(page: Int) = popularAnimeRequest(page)
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/ultimos-animes?page=$page")
override fun latestUpdatesSelector() = popularAnimeSelector()
override fun setupPreferenceScreen(screen: PreferenceScreen) {
val videoQualityPref = ListPreference(screen.context).apply {
key = "preferred_quality"
ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY
title = "Preferred quality"
entries = arrayOf("Fastream:720p", "Fastream:480p", "Fastream:360p")
entryValues = arrayOf("Fastream:720p", "Fastream:480p", "Fastream:360p")
setDefaultValue("Fastream:720p")
entries = QUALITY_LIST
entryValues = QUALITY_LIST
setDefaultValue(PREF_QUALITY_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
@ -169,7 +171,22 @@ class Animeyt : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
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()
}
screen.addPreference(videoQualityPref)
}.also(screen::addPreference)
}
}

View File

@ -5,12 +5,28 @@ ext {
extName = 'AsiaLiveAction'
pkgNameSuffix = 'es.asialiveaction'
extClass = '.AsiaLiveAction'
extVersionCode = 21
extVersionCode = 22
libVersion = '13'
}
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-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 androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.es.asialiveaction.extractors.VidGuardExtractor
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
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.Video
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.util.asJsoup
import kotlinx.serialization.json.Json
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
@ -21,7 +37,7 @@ import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.lang.Exception
import uy.kohesive.injekt.injectLazy
import java.util.Calendar
class AsiaLiveAction : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
@ -36,10 +52,28 @@ class AsiaLiveAction : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val client: OkHttpClient = network.cloudflareClient
private val json: Json by injectLazy()
private val preferences: SharedPreferences by lazy {
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 popularAnimeRequest(page: Int): Request = GET("$baseUrl/todos/page/$page")
@ -58,7 +92,7 @@ class AsiaLiveAction : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val anime = SAnime.create()
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.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() }
val year = document.select("header div.asia-post-main p.Info span.Date a").text().toInt()
val currentYear = Calendar.getInstance().get(Calendar.YEAR)
@ -74,38 +108,137 @@ class AsiaLiveAction : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
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 {
val episode = SEpisode.create()
val epNum = getNumberFromEpsString(element.select("div.flex-grow-1 p").text())
episode.setUrlWithoutDomain(element.attr("href"))
episode.episode_number = when {
return if (element.attr("class").contains("accordion")) {
val epNum = getNumberFromEpsString(element.select("label span").text())
SEpisode.create().apply {
name = element.select("label span").text().trim()
episode_number = when {
epNum.isNotEmpty() -> epNum.toFloatOrNull() ?: 1F
else -> 1F
}
episode.name = element.select("div.flex-grow-1 p").text().trim()
return episode
setUrlWithoutDomain(element.selectFirst("ul li a")?.attr("abs:href")!!)
}
} 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 {
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> {
val document = response.asJsoup()
val videoList = mutableListOf<Video>()
document.select("script").forEach { script ->
if (script.data().contains("var videosJap = [") || script.data().contains("var videosCor = [")) {
val content = script.data()
document.select("script:containsData(var videos)").forEach { script ->
fetchUrls(script.data()).map { url ->
try {
serverVideoResolver(url).also(videoList::addAll)
} catch (_: Exception) {}
}
}
return videoList
}
if (content.contains("okru")) {
val url = content.substringAfter(",['OK','").substringBefore("',0,0]")
val videos = OkruExtractor(client).videosFromUrl(url)
videoList.addAll(videos)
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 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
}
@ -116,24 +249,15 @@ class AsiaLiveAction : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
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:1080p")
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" }
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 searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
@ -201,19 +325,12 @@ class AsiaLiveAction : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun latestUpdatesSelector() = popularAnimeSelector()
override fun setupPreferenceScreen(screen: PreferenceScreen) {
val qualities = arrayOf(
"Okru:1080p",
"Okru:720p",
"Okru:480p",
"Okru:360p",
"Okru:240p", // Okru
)
val videoQualityPref = ListPreference(screen.context).apply {
key = "preferred_quality"
ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY
title = "Preferred quality"
entries = qualities
entryValues = qualities
setDefaultValue("Okru:1080p")
entries = QUALITY_LIST
entryValues = QUALITY_LIST
setDefaultValue(PREF_QUALITY_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
@ -222,7 +339,22 @@ class AsiaLiveAction : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
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()
}
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'
pkgNameSuffix = 'es.cuevana'
extClass = '.CuevanaFactory'
extVersionCode = 23
extVersionCode = 24
libVersion = '13'
}
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(path: ':lib-okru-extractor')
implementation project(path: ':lib-voe-extractor')
implementation project(path: ':lib-streamtape-extractor')
implementation project(path: ':lib-filemoon-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 "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.Video
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.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.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.voeextractor.VoeExtractor
import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor
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.JsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
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)
}
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 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
}
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 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.name = "T$noSeason - Episodio $epNum"
if (epDate != null) episode.date_upload = epDate.time
@ -112,21 +147,87 @@ class CuevanaCh(override val name: String, override val baseUrl: String) : Confi
} else {
""
}
} catch (e: Exception) { "" }
} catch (e: Exception) {
""
}
val url = it.attr("abs:data-video")
try {
loadExtractor(url, langPrefix).map { video -> videoList.add(video) }
serverVideoResolver(url, langPrefix).also(videoList::addAll)
} catch (_: Exception) { }
}
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 embedUrl = url.lowercase()
if (embedUrl.contains("tomatomatela")) {
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 headers = headers.newBuilder()
.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 file = json["file"]!!.jsonPrimitive!!.content
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) { }
}
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
}
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 videoUrlParse(document: Document) = 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 {
val filterList = if (filters.isEmpty()) getFilterList() else filters
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>>) :
AnimeFilter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
val qualities = arrayOf(
"Streamlare:1080p", "Streamlare:720p", "Streamlare:480p", "Streamlare:360p", "Streamlare:240p", // Streamlare
"StreamTape", "Amazon", "Voex", "DoodStream", "YourUpload",
)
val videoQualityPref = ListPreference(screen.context).apply {
key = "preferred_quality"
title = "Preferred quality"
entries = qualities
entryValues = qualities
setDefaultValue("Voex")
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 ->
@ -299,7 +364,38 @@ class CuevanaCh(override val name: String, override val baseUrl: String) : Confi
val entry = entryValues[index] as String
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
private val json = Json {
ignoreUnknownKeys = true
}
private val json = Json { ignoreUnknownKeys = true }
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)
}
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 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 ->
val anime = SAnime.create()
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.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> {
val episodes = mutableListOf<SEpisode>()
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 responseJson = json.decodeFromString<AnimeEpisodesList>(script)
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)
}
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")) {
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")) {
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
}
@ -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 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
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 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 script = document.selectFirst("script:containsData({\"props\":{\"pageProps\":{)")!!.data()
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
newAnime.status = SAnime.UNKNOWN
newAnime.title = data?.titles?.name ?: ""
newAnime.description = data?.overview ?: ""
newAnime.thumbnail_url = data?.images?.poster?.replace("/original/", "/w500/")
newAnime.genre = data?.genres?.joinToString { it.name ?: "" }
newAnime.artist = data?.cast?.acting?.firstOrNull()?.name ?: ""
newAnime.setUrlWithoutDomain(response.request.url.toString())
} else {
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.thumbnail_url = data?.images?.poster?.replace("/original/", "/w500/")
newAnime.genre = data?.genres?.joinToString { it.name ?: "" }
newAnime.artist = data?.cast?.acting?.firstOrNull()?.name ?: ""
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) {
val qualities = arrayOf(
"Streamlare:1080p", "Streamlare:720p", "Streamlare:480p", "Streamlare:360p", "Streamlare:240p", // Streamlare}
"Okru:1080p", "Okru:720p", "Okru:480p", "Okru:360p", "Okru:240p", // Okru
"StreamTape", "Amazon", "Voex", "DoodStream", "YourUpload", "Filemoon", "StreamWish", "Tomatomatela", "YourUpload",
)
val videoQualityPref = ListPreference(screen.context).apply {
key = "preferred_quality"
title = "Preferred quality"
entries = qualities
entryValues = qualities
setDefaultValue("Voex")
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 ->
@ -361,7 +373,38 @@ class CuevanaEu(override val name: String, override val baseUrl: String) : Confi
val entry = entryValues[index] as String
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 {
override fun createSources(): List<AnimeSource> = listOf(
CuevanaCh("Cuevana3Ch", "https://www12.cuevana3.ch"),
CuevanaEu("Cuevana3Eu", "https://www.cuevana-3.eu"),
CuevanaCh("Cuevana3Ch", "https://ww1.cuevana3.ch"),
CuevanaEu("Cuevana3Eu", "https://www.cuevana3.eu"),
)
}

View File

@ -5,17 +5,27 @@ ext {
extName = 'Doramasflix'
pkgNameSuffix = 'es.doramasflix'
extClass = '.Doramasflix'
extVersionCode = 10
extVersionCode = 11
libVersion = '13'
}
dependencies {
implementation(project(':lib-vudeo-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-okru-extractor'))
implementation(project(':lib-dood-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"

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.Video
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.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.voeextractor.VoeExtractor
import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.util.asJsoup
@ -26,6 +35,7 @@ import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
@ -55,6 +65,29 @@ class Doramasflix : ConfigurableAnimeSource, AnimeHttpSource() {
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 {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
@ -89,21 +122,21 @@ class Doramasflix : ConfigurableAnimeSource, AnimeHttpSource() {
if (el.data().contains("{\"props\":{\"pageProps\":{")) {
val apolloState = json.decodeFromString<JsonObject>(el.data())!!.jsonObject["props"]!!.jsonObject["pageProps"]!!.jsonObject["apolloState"]!!.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 network = try { apolloState.entries.firstOrNull { x -> x.key.contains("networks") }?.value?.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) { null }
val type = dorama["__typename"]!!.jsonPrimitive.content.lowercase()
val poster = dorama["poster_path"]!!.jsonPrimitive.content
val urlImg = poster.ifEmpty { dorama["poster"]!!.jsonPrimitive.content }
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) { "" }
val artist = try { dorama["cast"]?.jsonObject?.get("json")?.jsonArray?.firstOrNull()?.jsonObject?.get("name")?.jsonPrimitive?.content } catch (_: Exception) { "" }
val type = try { dorama["__typename"]!!.jsonPrimitive.content.lowercase() } catch (_: Exception) { "" }
val poster = try { dorama["poster_path"]!!.jsonPrimitive.content } catch (_: Exception) { "" }
val urlImg = try { poster.ifEmpty { dorama["poster"]!!.jsonPrimitive.content } } catch (_: Exception) { "" }
val id = dorama["_id"]!!.jsonPrimitive!!.content
anime.title = "${dorama["name"]!!.jsonPrimitive!!.content} (${dorama["name_es"]!!.jsonPrimitive!!.content})"
anime.description = dorama["overview"]!!.jsonPrimitive!!.content.trim()
anime.genre = genres
anime.author = network
anime.artist = artist
anime.status = if (type == "movie") SAnime.COMPLETED else SAnime.UNKNOWN
anime.thumbnail_url = externalOrInternalImg(urlImg)
val id = dorama["_id"]!!.jsonPrimitive.content
anime.title = "${dorama["name"]?.jsonPrimitive?.content} (${dorama["name_es"]?.jsonPrimitive?.content})"
anime.description = dorama["overview"]?.jsonPrimitive?.content?.trim() ?: ""
if (genres.isNotEmpty()) anime.genre = genres
if (network.isNotEmpty()) anime.author = network
if (artist != null) anime.artist = artist
if (type.isNotEmpty()) anime.status = if (type == "movie") SAnime.COMPLETED else SAnime.UNKNOWN
if (urlImg.isNotEmpty()) anime.thumbnail_url = externalOrInternalImg(urlImg)
anime.setUrlWithoutDomain(urlSolverByType(dorama["__typename"]!!.jsonPrimitive!!.content, dorama["slug"]!!.jsonPrimitive!!.content, id))
}
}
@ -196,6 +229,25 @@ class Doramasflix : ConfigurableAnimeSource, AnimeHttpSource() {
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 {
val jsonData = jsonLine ?: return AnimesPage(emptyList(), false)
val animeList = mutableListOf<SAnime>()
@ -212,7 +264,6 @@ class Doramasflix : ConfigurableAnimeSource, AnimeHttpSource() {
anime.description = it.jsonObject!!["overview"]!!.jsonPrimitive!!.content
anime.genre = genres
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))
animeList.add(anime)
}
@ -299,52 +350,146 @@ class Doramasflix : ConfigurableAnimeSource, AnimeHttpSource() {
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> {
val document = response.asJsoup()
val videos = mutableListOf<Video>()
val script = document.selectFirst("script:containsData({\"props\":{\"pageProps\":{)")!!.data()
fetchUrls(script).map { link ->
resolveVideoServer(link).let { videos.addAll(it) }
val videoList = mutableListOf<Video>()
val jsonData = document.selectFirst("script:containsData({\"props\":{\"pageProps\":{)")!!.data()
val apolloState = json.decodeFromString<JsonObject>(jsonData).jsonObject["props"]!!.jsonObject["pageProps"]!!.jsonObject["apolloState"]!!.jsonObject
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> {
return runCatching {
when {
"streamtape" in link ->
StreamTapeExtractor(client).videoFromUrl(link)?.let(::listOf)
"mixdrop" in link ->
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
private fun serverVideoResolver(url: String, prefix: String = ""): List<Video> {
val videoList = mutableListOf<Video>()
val embedUrl = url.lowercase()
try {
if (embedUrl.contains("voe")) {
VoeExtractor(client).videoFromUrl(url, prefix = "$prefix Voe:")?.let { videoList.add(it) }
}
}.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) {
val qualities = arrayOf(
"Okru:1080p", "Okru:720p", "Okru:480p", "Okru:360p", "Okru:240p", "Okru:144p", // Okru
"Streamlare:1080p", "Streamlare:720p", "Streamlare:480p", "Streamlare:360p", "Streamlare:240p", // Streamlare
"StreamTape", "Voex", "Uqload", "MixDrop",
)
val videoQualityPref = ListPreference(screen.context).apply {
key = "preferred_quality"
title = "Preferred quality"
entries = qualities
entryValues = qualities
setDefaultValue("Voex")
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 ->
@ -353,7 +498,38 @@ class Doramasflix : ConfigurableAnimeSource, AnimeHttpSource() {
val entry = entryValues[index] as String
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'
pkgNameSuffix = 'es.gnula'
extClass = '.Gnula'
extVersionCode = 8
extVersionCode = 9
libVersion = '13'
}
@ -15,6 +15,15 @@ dependencies {
implementation project(path: ':lib-yourupload-extractor')
implementation project(path: ':lib-voe-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.Video
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.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.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.voeextractor.VoeExtractor
import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
@ -50,12 +59,29 @@ class Gnula : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
private val json: Json by injectLazy()
private val langValues = arrayOf("latino", "spanish", "english", "")
private val preferences: SharedPreferences by lazy {
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 popularAnimeParse(response: Response): AnimesPage {
@ -130,7 +156,6 @@ class Gnula : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup()
val videoList = mutableListOf<Video>()
val langSelect = preferences.getString("preferred_lang", "latino").toString()
if (response.request.url.toString().contains("/movies/")) {
document.select("script").forEach { script ->
@ -139,26 +164,11 @@ class Gnula : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val props = jObject["props"]!!.jsonObject
val pageProps = props["pageProps"]!!.jsonObject
val post = pageProps["post"]!!.jsonObject
var players = if (langSelect != "") {
post["players"]!!.jsonObject.entries.filter { x -> x.key == langSelect && x.value.jsonArray.any() }.toList()
} else {
post["players"]!!.jsonObject.entries.toList()
}
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)
post["players"]!!.jsonObject.entries.map {
val key = it.key
val langVal = try {
LANGUAGE_LIST[LANGUAGE_LIST.indexOf(LANGUAGE_LIST_VALUES.firstOrNull { it == key })]
} catch (_: Exception) { "" }
it.value!!.jsonArray!!.map {
val server = it!!.jsonObject["result"]!!.jsonPrimitive!!.content
var url = ""
@ -167,7 +177,7 @@ class Gnula : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
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 pageProps = props["pageProps"]!!.jsonObject
val episode = pageProps["episode"]!!.jsonObject
val players = episode["players"]!!.jsonObject.entries.filter { x -> x.key == langSelect }
.ifEmpty { episode["players"]!!.jsonObject.entries }
val players = episode["players"]!!.jsonObject.entries
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 {
val server = it!!.jsonObject["result"]!!.jsonPrimitive!!.content
var url = ""
@ -191,7 +204,7 @@ class Gnula : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
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
}
private fun loadExtractor(url: String, prefix: String = ""): List<Video> {
private fun serverVideoResolver(url: String, prefix: String = ""): List<Video> {
val videoList = mutableListOf<Video>()
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")) {
runCatching {
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 (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, quality = "$prefix Voex")?.let { videoList.add(it) }
}
if (embedUrl.contains("streamtape")) {
StreamTapeExtractor(client).videoFromUrl(url, quality = "$prefix StreamTape")?.let { videoList.add(it) }
}
} catch (_: Exception) { }
return videoList
}
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:1080p")
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" }
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 searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
@ -339,32 +395,12 @@ class Gnula : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
val qualities = arrayOf(
"Okru:1080p", "Okru:720p", "Okru:480p", "Okru:360p", "Okru:240p", "Okru:144p", // Okru
"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"
ListPreference(screen.context).apply {
key = PREF_LANGUAGE_KEY
title = "Preferred language"
entries = arrayOf("Latino", "Español", "Subtitulado", "Todos")
entryValues = langValues
setDefaultValue("latino")
entries = LANGUAGE_LIST
entryValues = LANGUAGE_LIST
setDefaultValue(PREF_LANGUAGE_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
@ -373,10 +409,39 @@ class Gnula : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}
}.also(screen::addPreference)
screen.addPreference(videoQualityPref)
screen.addPreference(langPref)
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()
}
}.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

View File

@ -3,10 +3,10 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext {
extName = 'Hentaila'
extName = 'HentaiLA'
pkgNameSuffix = 'es.hentaila'
extClass = '.Hentaila'
extVersionCode = 17
extVersionCode = 18
libVersion = '13'
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 java.io.IOException
import java.lang.Exception
import java.text.SimpleDateFormat
import java.util.Locale
class Hentaila : ConfigurableAnimeSource, AnimeHttpSource() {
@ -50,36 +52,43 @@ class Hentaila : ConfigurableAnimeSource, AnimeHttpSource() {
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 {
val document = response.asJsoup()
val elements = document.select("section.latest-hentais div.slider > div.item")
val animes = elements.map { element ->
SAnime.create().apply {
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 ->
val elements = document.select(".hentais .hentai")
val nextPage = document.select(".pagination .fa-arrow-right").any()
val animeList = elements.map { element ->
SAnime.create().apply {
setUrlWithoutDomain(element.select("a").attr("abs:href"))
title = element.selectFirst("h2.h-title")!!.text()
thumbnail_url = element.selectFirst("figure img")!!.attr("abs:src")
title = element.selectFirst(".h-header .h-title")!!.text()
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 {
val filterList = if (filters.isEmpty()) getFilterList() else filters
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)
}
@ -179,6 +188,10 @@ class Hentaila : ConfigurableAnimeSource, AnimeHttpSource() {
episode_number = epNum.toFloat()
name = "Episodio $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)
}
@ -199,10 +212,10 @@ class Hentaila : ConfigurableAnimeSource, AnimeHttpSource() {
when (nameServer.lowercase()) {
"streamwish" -> {
videoList.addAll(StreamWishExtractor(client, headers).videosFromUrl(urlServer, "StreamWish "))
videoList.addAll(StreamWishExtractor(client, headers).videosFromUrl(urlServer, videoNameGen = { "StreamWish:$it" }))
}
"voe" -> {
val video = VoeExtractor(client).videoFromUrl(urlServer)
val video = VoeExtractor(client).videoFromUrl(urlServer, prefix = "Voe:")
if (video != null) videoList.add(video)
}
"arc" -> {
@ -225,29 +238,15 @@ class Hentaila : ConfigurableAnimeSource, AnimeHttpSource() {
}
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) },
).toMutableList()
val userPreferredQuality = preferences.getString("preferred_quality", "YourUpload")
val newList = mutableListOf<Video>()
var preferred = 0
for (video in videoSorted) {
if (video.quality.startsWith(userPreferredQuality!!)) {
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()
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 getFilterList(): AnimeFilterList = AnimeFilterList(
@ -321,19 +320,12 @@ class Hentaila : ConfigurableAnimeSource, AnimeHttpSource() {
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
val qualities = arrayOf(
"YourUpload",
"BurstCloud",
"Voe",
"StreamWish",
"Mp4Upload",
)
val videoQualityPref = ListPreference(screen.context).apply {
key = "preferred_quality"
title = "Preferred quality"
entries = qualities
entryValues = qualities
setDefaultValue("YourUpload")
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 ->
@ -342,7 +334,22 @@ class Hentaila : ConfigurableAnimeSource, AnimeHttpSource() {
val entry = entryValues[index] as String
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'
pkgNameSuffix = 'es.jkanime'
extClass = '.Jkanime'
extVersionCode = 16
extVersionCode = 17
libVersion = '13'
}
dependencies {
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.Video
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.network.GET
import eu.kanade.tachiyomi.util.asJsoup
@ -23,7 +24,6 @@ import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.lang.Exception
class Jkanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
@ -41,6 +41,26 @@ class Jkanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
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 popularAnimeRequest(page: Int): Request = GET("$baseUrl/top/")
@ -99,7 +119,8 @@ class Jkanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val videos = mutableListOf<Video>()
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 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 url = scriptServers.data().substringAfter("video[$serverId] = '<iframe class=\"player_conte\" src=\"")
.substringBefore("\"")
@ -107,12 +128,15 @@ class Jkanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
.replace("/jkvmixdrop.php?u=", "https://mixdrop.co/e/")
.replace("/jk.php?u=", "$baseUrl/")
try {
when {
"ok" in url -> OkruExtractor(client).videosFromUrl(url, lang).forEach { videos.add(it) }
"stream/jkmedia" in url -> videos.add(Video(url, "${lang}Xtreme S", url))
"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) }
"ok" in url -> OkruExtractor(client).videosFromUrl(url, "$lang ").forEach { videos.add(it) }
"mixdrop" in url -> MixDropExtractor(client).videosFromUrl(url, prefix = "$lang ").forEach { videos.add(it) }
"stream/jkmedia" in url -> videos.add(Video(url, "$lang Xtreme S", url))
"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
}
@ -121,21 +145,18 @@ class Jkanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun videoUrlParse(document: Document) = 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> {
val quality = preferences.getString("preferred_quality", "Nozomi")!!
return sortIfContains(quality)
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 searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
@ -248,12 +269,10 @@ class Jkanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun latestUpdatesFromElement(element: Element): SAnime {
val anime = SAnime.create()
anime.setUrlWithoutDomain(
element.select("div.row.g-0 div.col-md-5.custom_thumb2 a").attr("href"),
)
anime.title = element.select("div.row.g-0 div.col-md-7 div.card-body h5.card-title a").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()
anime.setUrlWithoutDomain(element.select(".custom_thumb2 > a").attr("abs:href"))
anime.title = element.select(".card-title > a").text()
anime.thumbnail_url = element.select(".custom_thumb2 a img").attr("abs:src")
anime.description = element.select(".synopsis").text()
return anime
}
@ -393,16 +412,12 @@ class Jkanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
val qualities = arrayOf(
"Okru:1080p", "Okru:720p", "Okru:480p", "Okru:360p", "Okru:240p", // Okru
"Xtreme S", "HentaiJk", "Nozomi", "Desu", // video servers without resolution
)
val videoQualityPref = ListPreference(screen.context).apply {
key = "preferred_quality"
title = "Preferred quality"
entries = qualities
entryValues = qualities
setDefaultValue("Nozomi")
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 ->
@ -411,7 +426,38 @@ class Jkanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val entry = entryValues[index] as String
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'
pkgNameSuffix = 'es.locopelis'
extClass = '.LocoPelis'
extVersionCode = 17
extVersionCode = 18
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.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.json.Json
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
@ -24,7 +23,6 @@ import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
import kotlin.Exception
@ -40,12 +38,20 @@ class LocoPelis : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override val client: OkHttpClient = network.cloudflareClient
private val json: Json by injectLazy()
private val preferences: SharedPreferences by lazy {
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 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 ->
val url = iframe.attr("src")
val embedUrl = url.lowercase()
if (url.lowercase().contains("streamtape")) {
val video = StreamTapeExtractor(client).videoFromUrl(url, "Streamtape")
if (embedUrl.contains("streamtape")) {
val video = StreamTapeExtractor(client).videoFromUrl(url, "StreamTape")
if (video != null) {
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 {
DoodExtractor(client).videoFromUrl(url, "DoodStream", true)
} catch (e: Exception) {
@ -107,7 +116,7 @@ class LocoPelis : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
videoList.add(video)
}
}
if (url.lowercase().contains("okru")) {
if (embedUrl.contains("okru") || embedUrl.contains("ok.ru")) {
val videos = OkruExtractor(client).videosFromUrl(url)
videoList.addAll(videos)
}
@ -121,23 +130,6 @@ class LocoPelis : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
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 {
return epsStr.filter { it.isDigit() }.ifEmpty { "0" }
}
@ -242,25 +234,25 @@ class LocoPelis : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
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) {
val videoQualityPref = ListPreference(screen.context).apply {
key = "preferred_quality"
title = "Preferred quality"
entries = arrayOf(
"Okru:1080p", "Okru:720p", "Okru:480p", "Okru:360p", "Okru:240p", "Okru:144p", // Okru
"YourUpload", "DoodStream", "StreamTape",
) // 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")
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 ->
@ -269,7 +261,22 @@ class LocoPelis : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
val entry = entryValues[index] as String
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'
pkgNameSuffix = 'es.metroseries'
extClass = '.MetroSeries'
extVersionCode = 1
extVersionCode = 2
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.network.GET
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.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
import java.util.Locale
@ -40,8 +43,6 @@ class MetroSeries : ConfigurableAnimeSource, AnimeHttpSource() {
override val lang = "es"
private val json: Json by injectLazy()
override val supportsLatest = false
override val client: OkHttpClient = network.cloudflareClient
@ -50,6 +51,25 @@ class MetroSeries : ConfigurableAnimeSource, AnimeHttpSource() {
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 popularAnimeParse(response: Response): AnimesPage {
@ -86,14 +106,29 @@ class MetroSeries : ConfigurableAnimeSource, AnimeHttpSource() {
}
override fun episodeListParse(response: Response): List<SEpisode> {
val episodes = mutableListOf<SEpisode>()
val document = response.asJsoup()
document.select(".season-list li a")
.sortedByDescending { it.attr("data-season") }.map {
val post = it.attr("data-post")
val season = it.attr("data-season")
val referer = response.request.url.toString()
val chunkSize = Runtime.getRuntime().availableProcessors()
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()
.add("action", "action_select_season")
.add("season", season)
@ -105,26 +140,51 @@ class MetroSeries : ConfigurableAnimeSource, AnimeHttpSource() {
.url("https://metroseries.net/wp-admin/admin-ajax.php")
.post(formBody)
.header("Origin", baseUrl)
.header("Referer", response.request.url.toString())
.header("Referer", referer)
.header("Content-Type", "application/x-www-form-urlencoded")
.build()
val docEpisodes = client.newCall(request).execute().asJsoup()
val detail = client.newCall(request).execute().asJsoup()
docEpisodes.select(".episodes-list li a").reversed().map {
val epNumber = it.ownText().substringAfter("x").substringBefore("").trim()
val episode = SEpisode.create().apply {
setUrlWithoutDomain(it.attr("abs:href"))
name = "T$season - E$epNumber - ${it.ownText().substringAfter("").trim()}"
date_upload = try {
SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH).parse(it.select("span").text()).time
} catch (_: Exception) { System.currentTimeMillis() }
}
episodes.add(episode)
val firstPage = try { detail.selectFirst("#aa-season > nav > span.page-numbers")?.text()?.toInt() ?: 1 } catch (_: Exception) { 1 }
val lastPage = try { detail.select(".pagination a.page-numbers:not(.next)").last()?.text()?.toInt() ?: firstPage } catch (_: Exception) { firstPage }
return firstPage.rangeTo(lastPage)
} catch (_: Exception) {
return 1..1
}
}
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
}
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> {
val document = response.asJsoup()
val videoList = mutableListOf<Video>()
@ -180,7 +240,7 @@ class MetroSeries : ConfigurableAnimeSource, AnimeHttpSource() {
BurstCloudExtractor(client).videoFromUrl(src, headers = headers).let { videoList.addAll(it) }
}
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) {}
}
@ -189,41 +249,24 @@ class MetroSeries : ConfigurableAnimeSource, AnimeHttpSource() {
}
override fun List<Video>.sort(): List<Video> {
val quality = preferences.getString("preferred_quality", "Fastream:1080p")
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
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) {
val qualities = arrayOf(
"YourUpload",
"BurstCloud",
"Voe",
"StreamWish",
"Mp4Upload",
"Fastream:1080p",
"Fastream:720p",
"Fastream:480p",
"Fastream:360p",
)
val videoQualityPref = ListPreference(screen.context).apply {
key = "preferred_quality"
ListPreference(screen.context).apply {
key = PREF_QUALITY_KEY
title = "Preferred quality"
entries = qualities
entryValues = qualities
setDefaultValue("Fastream:1080p")
entries = QUALITY_LIST
entryValues = QUALITY_LIST
setDefaultValue(PREF_QUALITY_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
@ -232,7 +275,39 @@ class MetroSeries : ConfigurableAnimeSource, AnimeHttpSource() {
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)
}
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'
pkgNameSuffix = 'es.pelisforte'
extClass = '.PelisForte'
extVersionCode = 3
extVersionCode = 4
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_DEFAULT = "StreamWish"
private val SERVER_LIST = arrayOf(
"YourUpload", "BurstCloud", "Voe", "Mp4Upload", "Doodstream",
"Upload", "BurstCloud", "Upstream", "StreamTape", "Doodstream",
"YourUpload", "Voe", "Mp4Upload", "Doodstream",
"Upload", "BurstCloud", "Upstream", "StreamTape",
"Fastream", "Filemoon", "StreamWish", "Okru",
)
}
@ -183,7 +183,7 @@ open class PelisForte : ConfigurableAnimeSource, AnimeHttpSource() {
val embedUrl = url.lowercase()
try {
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")) {
OkruExtractor(client).videosFromUrl(url, prefix).also(videoList::addAll)
@ -203,7 +203,7 @@ open class PelisForte : ConfigurableAnimeSource, AnimeHttpSource() {
val docHeaders = headers.newBuilder()
.add("Referer", referer)
.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.")) {
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) {
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 {
key = PREF_QUALITY_KEY
title = "Preferred quality"
@ -313,21 +329,5 @@ open class PelisForte : ConfigurableAnimeSource, AnimeHttpSource() {
preferences.edit().putString(key, entry).commit()
}
}.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'
pkgNameSuffix = 'es.pelisplushd'
extClass = '.PelisplushdFactory'
extVersionCode = 44
extVersionCode = 45
libVersion = '13'
}
@ -20,6 +20,11 @@ dependencies {
implementation(project(':lib-dood-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 "dev.datlag.jsunpacker:jsunpacker:1.0.1"
}

View File

@ -5,6 +5,7 @@ import android.content.SharedPreferences
import android.util.Base64
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.es.pelisplushd.extractors.StreamHideExtractor
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
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.Video
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.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.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.voeextractor.VoeExtractor
import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor
import eu.kanade.tachiyomi.network.GET
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.Request
import okhttp3.Response
@ -28,6 +39,7 @@ import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
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
private val json: Json by injectLazy()
val preferences: SharedPreferences by lazy {
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 popularAnimeRequest(page: Int): Request = GET("$baseUrl/series?page=$page")
override fun popularAnimeFromElement(element: Element): SAnime {
val anime = SAnime.create()
anime.setUrlWithoutDomain(
element.select("a").attr("href"),
)
anime.title = element.select("a div.listing-content p").text()
anime.thumbnail_url = element.select("a img").attr("src").replace("/w154/", "/w200/")
return anime
return SAnime.create().apply {
setUrlWithoutDomain(element.select("a").attr("abs:href"))
title = element.select("a div.listing-content p").text()
thumbnail_url = element.select("a img").attr("src").replace("/w154/", "/w200/")
}
}
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()
if (response.request.url.toString().contains("/pelicula/")) {
val episode = SEpisode.create().apply {
val epnum = 1
episode_number = epnum.toFloat()
episode_number = 1F
name = "PELÍCULA"
setUrlWithoutDomain(response.request.url.toString())
}
episode.setUrlWithoutDomain(response.request.url.toString())
episodes.add(episode)
} else {
jsoup.select("div.tab-content div a").forEachIndexed { index, element ->
val epNum = index + 1
val episode = SEpisode.create()
episode.episode_number = epNum.toFloat()
episode.name = element.text()
episode.setUrlWithoutDomain(element.attr("href"))
val episode = SEpisode.create().apply {
episode_number = (index + 1).toFloat()
name = element.text()
setUrlWithoutDomain(element.attr("abs:href"))
}
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 alternativeServers = document.select("ul.TbVideoNv.nav.nav-tabs li:not(:first-child)")
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 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()
@ -114,12 +137,12 @@ open class Pelisplushd(override val name: String, override val baseUrl: String)
}
if (!url.contains("?data=")) {
serverVideoResolver(url, server)?.forEach { video -> videoList.add(video) }
serverVideoResolver(url)?.forEach { video -> videoList.add(video) }
} else {
val apiPageSoup = client.newCall(GET(url)).execute().asJsoup()
val realUrl = apiPageSoup.selectFirst("iframe")?.attr("src")
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" }
}
}
serverVideoResolver(serverUrl, serverName)?.forEach { video -> videoList.add(video) }
serverVideoResolver(serverUrl)?.forEach { video -> videoList.add(video) }
}
}
return videoList
}
private fun bytesToHex(bytes: ByteArray): String {
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>? {
private fun serverVideoResolver(url: String): List<Video> {
val videoList = mutableListOf<Video>()
val embedUrl = url.lowercase()
try {
if (server.lowercase() == "stp") {
StreamTapeExtractor(client).videoFromUrl(url, "StreamTape")?.let { videoList.add(it) }
} else if (server.lowercase() == "uwu") {
if (!url.contains("disable")) {
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()
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"))
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 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("\"")
val videoUrl = amazonApi.toString().substringAfter("\"FOLDER\":").substringAfter("tempLink\":\"").substringBefore("\"")
videoList.add(Video(videoUrl, "Amazon", videoUrl))
}
}
} else if (server.lowercase() == "voex") {
VoeExtractor(client).videoFromUrl(url, "Voex")?.let { videoList.add(it) }
} else if (server.lowercase() == "streamlare") {
videoList.addAll(StreamlareExtractor(client).videosFromUrl(url))
} else if (server.lowercase() == "doodstream") {
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) }
} else if (server.lowercase() == "upload") {
return YourUploadExtractor(client).videoFromUrl(url, headers = headers)
} else if (server.lowercase().contains("streamwish")) {
val docHeaders = headers.newBuilder()
.add("Referer", "$baseUrl/")
}
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("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()
StreamWishExtractor(client, docHeaders).videosFromUrl(url, "StreamWish")
} else if (server.contains("filemoon") || server.contains("moonplayer")) {
FilemoonExtractor(client).videosFromUrl(url, headers = headers).also(videoList::addAll)
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, "Tomatomatela", file, headers = null)) }
}
}
} catch (_: Exception) { }
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 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", "Voex")
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
}
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()
}
fun getNumberFromString(epsStr: String): String {
return epsStr.filter { it.isDigit() }.ifEmpty { "0" }
}
fun getNumberFromString(epsStr: String) = epsStr.filter { it.isDigit() }.ifEmpty { "0" }
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
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")
}
}
override fun searchAnimeFromElement(element: Element): SAnime {
return popularAnimeFromElement(element)
}
override fun searchAnimeFromElement(element: Element) = popularAnimeFromElement(element)
override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector()
override fun searchAnimeSelector(): String = popularAnimeSelector()
override fun animeDetailsParse(document: Document): SAnime {
val anime = SAnime.create()
anime.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")!!
return SAnime.create().apply {
title = document.selectFirst("h1.m-b-5")!!.text()
thumbnail_url = document.selectFirst("div.card-body div.row div.col-sm-3 img.img-fluid")!!
.attr("src").replace("/w154/", "/w500/")
anime.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() }
anime.status = SAnime.COMPLETED
return anime
description = document.selectFirst("div.col-sm-4 div.text-large")!!.ownText()
genre = document.select("div.p-v-20.p-h-15.text-center a span").joinToString { it.text() }
status = SAnime.COMPLETED
}
}
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(
"Tipos",
"Géneros",
arrayOf(
Pair("<selecionar>", ""),
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)
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()) {
fun toUriPart() = vals[state].second
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
val qualities = arrayOf(
"Streamlare:1080p", "Streamlare:720p", "Streamlare:480p", "Streamlare:360p", "Streamlare:240p", // Streamlare
"StreamTape", "Amazon", "Voex", "DoodStream", "YourUpload",
)
val videoQualityPref = ListPreference(screen.context).apply {
key = "preferred_quality"
title = "Preferred quality"
entries = qualities
entryValues = qualities
setDefaultValue("Voex")
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 ->
@ -333,7 +386,22 @@ open class Pelisplushd(override val name: String, override val baseUrl: String)
val entry = entryValues[index] as String
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.SEpisode
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.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.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.voeextractor.VoeExtractor
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.JsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
@ -33,6 +39,21 @@ class Pelisplusph(override val name: String, override val baseUrl: String) : Pel
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 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 {
episode_number = 1F
name = "PELÍCULA"
setUrlWithoutDomain(response.request.url.toString())
}
episode.setUrlWithoutDomain(response.request.url.toString())
episodes.add(episode)
} else {
var index = 0
@ -84,14 +105,14 @@ class Pelisplusph(override val name: String, override val baseUrl: String) : Pel
}.getOrElse { idxSeas + 1 }
season.select(".item-season-episodes a").reversed().mapIndexed { idx, ep ->
index += 1
val noEp = runCatching {
val noEp = try {
getNumberFromString(ep.ownText())
}.getOrElse { idx + 1 }
val episode = SEpisode.create()
episode.episode_number = index.toFloat()
episode.name = "T$seasonNumber - E$noEp - ${ep.ownText()}"
episode.setUrlWithoutDomain(baseUrl + ep.attr("href"))
} catch (_: Exception) { idx + 1 }
val episode = SEpisode.create().apply {
episode_number = index.toFloat()
name = "T$seasonNumber - E$noEp - ${ep.ownText()}"
setUrlWithoutDomain(ep.attr("abs:href"))
}
episodes.add(episode)
}
}
@ -115,10 +136,10 @@ class Pelisplusph(override val name: String, override val baseUrl: String) : Pel
document.select("[class*=server-item-]").map {
val langIdx = getNumberFromString(it.attr("class").substringAfter("server-item-"))
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 ->
val url = servers.attr("data-video")
loadExtractor(url, lang).let { videos ->
serverVideoResolver(url, lang).let { videos ->
videoList.addAll(videos)
}
}
@ -126,9 +147,76 @@ class Pelisplusph(override val name: String, override val baseUrl: String) : Pel
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 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")) {
runCatching {
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 (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, "$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)
}
} 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 getFilterList(): AnimeFilterList = AnimeFilterList(
AnimeFilter.Header("La busqueda por genero ignora los otros filtros"),
GenreFilter(),
@ -310,16 +378,12 @@ class Pelisplusph(override val name: String, override val baseUrl: String) : Pel
)
override fun setupPreferenceScreen(screen: PreferenceScreen) {
val qualities = arrayOf(
"DoodStream",
"Voex",
)
val videoQualityPref = ListPreference(screen.context).apply {
key = "preferred_quality"
title = "Preferred quality"
entries = qualities
entryValues = qualities
setDefaultValue("Voex")
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 ->
@ -328,7 +392,38 @@ class Pelisplusph(override val name: String, override val baseUrl: String) : Pel
val entry = entryValues[index] as String
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.SEpisode
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.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.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.voeextractor.VoeExtractor
import eu.kanade.tachiyomi.lib.vudeoextractor.VudeoExtractor
import eu.kanade.tachiyomi.lib.youruploadextractor.YourUploadExtractor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
@ -38,6 +43,16 @@ class Pelisplusto(override val name: String, override val baseUrl: String) : Pel
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 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 {
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.thumbnail_url = element.select("a .item__image picture img").attr("data-src")
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.description = document.selectFirst(".home__slider_content .description")!!.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
return anime
}
@ -68,8 +84,8 @@ class Pelisplusto(override val name: String, override val baseUrl: String) : Pel
val episode = SEpisode.create().apply {
episode_number = 1F
name = "PELÍCULA"
setUrlWithoutDomain(response.request.url.toString())
}
episode.setUrlWithoutDomain(response.request.url.toString())
episodes.add(episode)
} else {
var jsonscript = ""
@ -88,10 +104,11 @@ class Pelisplusto(override val name: String, override val baseUrl: String) : Pel
val season = jsonElement["season"]!!.jsonPrimitive!!.content
val title = jsonElement["title"]!!.jsonPrimitive!!.content
val ep = jsonElement["episode"]!!.jsonPrimitive!!.content
val episode = SEpisode.create()
episode.episode_number = index.toFloat()
episode.name = "T$season - E$ep - $title"
episode.setUrlWithoutDomain("${response.request.url}/season/$season/episode/$ep")
val episode = SEpisode.create().apply {
episode_number = index.toFloat()
name = "T$season - E$ep - $title"
setUrlWithoutDomain("${response.request.url}/season/$season/episode/$ep")
}
episodes.add(episode)
}
}
@ -126,7 +143,7 @@ class Pelisplusto(override val name: String, override val baseUrl: String) : Pel
fetchUrls(script).map {
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.*"), "")
loadExtractor(link).let { videos ->
serverVideoResolver(link).let { 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")
.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)
}
}
@ -143,11 +160,93 @@ class Pelisplusto(override val name: String, override val baseUrl: String) : Pel
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 embedUrl = url.lowercase()
if (embedUrl.contains("tomatomatela")) {
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 headers = headers.newBuilder()
.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 status = json["status"]!!.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) { }
}
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
}
@ -259,16 +320,12 @@ class Pelisplusto(override val name: String, override val baseUrl: String) : Pel
)
override fun setupPreferenceScreen(screen: PreferenceScreen) {
val qualities = arrayOf(
"Voex",
"DoodStream",
)
val videoQualityPref = ListPreference(screen.context).apply {
key = "preferred_quality"
title = "Preferred quality"
entries = qualities
entryValues = qualities
setDefaultValue("Voex")
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 ->
@ -277,7 +334,22 @@ class Pelisplusto(override val name: String, override val baseUrl: String) : Pel
val entry = entryValues[index] as String
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)
}
}