refactor(src/es): Improvements for the main spanish extensions (#2446)
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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'))
|
||||
}
|
||||
|
||||
|
||||
|
@ -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"),
|
||||
)
|
||||
title = element.select("div.title h3 a").text()
|
||||
thumbnail_url = element.select("figure.image a img").attr("src")
|
||||
description = element.select("div.serie-card__information p").text()
|
||||
}
|
||||
return anime
|
||||
}
|
||||
|
||||
override fun popularAnimeNextPageSelector(): String = "ul.pagination-list li a.pagination-link:contains(Siguiente)"
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
override fun popularAnimeParse(response: Response): AnimesPage {
|
||||
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"))
|
||||
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("abs:src")
|
||||
description = element.select("div.serie-card__information p").text()
|
||||
}
|
||||
}
|
||||
return AnimesPage(animeList, nextPage)
|
||||
}
|
||||
|
||||
override fun episodeListSelector() = throw Exception("not used")
|
||||
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/animes?order=added&page=$page")
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -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'
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
screen.addPreference(videoQualityPref)
|
||||
}.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()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ ext {
|
||||
extName = 'AnimeID'
|
||||
pkgNameSuffix = 'es.animeid'
|
||||
extClass = '.AnimeID'
|
||||
extVersionCode = 6
|
||||
extVersionCode = 7
|
||||
libVersion = '13'
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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,23 +85,35 @@ class AnimeLatinoHD : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
|
||||
val props = jObject["props"]!!.jsonObject
|
||||
val pageProps = props["pageProps"]!!.jsonObject
|
||||
val data = pageProps["data"]!!.jsonObject
|
||||
val popularToday = data["popular_today"]!!.jsonArray
|
||||
popularToday.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)
|
||||
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
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
screen.addPreference(videoQualityPref)
|
||||
}.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()
|
||||
}
|
||||
}.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)
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ ext {
|
||||
extName = 'AnimeMovil'
|
||||
pkgNameSuffix = 'es.animemovil'
|
||||
extClass = '.AnimeMovil'
|
||||
extVersionCode = 3
|
||||
extVersionCode = 4
|
||||
libVersion = '13'
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
|
@ -5,7 +5,7 @@ ext {
|
||||
extName = 'Animeyt'
|
||||
pkgNameSuffix = 'es.animeyt'
|
||||
extClass = '.Animeyt'
|
||||
extVersionCode = 7
|
||||
extVersionCode = 8
|
||||
libVersion = '13'
|
||||
}
|
||||
dependencies {
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -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'))
|
||||
}
|
||||
|
||||
|
||||
|
@ -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,41 +108,140 @@ 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 {
|
||||
epNum.isNotEmpty() -> epNum.toFloatOrNull() ?: 1F
|
||||
else -> 1F
|
||||
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
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
episode.name = element.select("div.flex-grow-1 p").text().trim()
|
||||
return episode
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
if (content.contains("okru")) {
|
||||
val url = content.substringAfter(",['OK','").substringBefore("',0,0]")
|
||||
val videos = OkruExtractor(client).videosFromUrl(url)
|
||||
videoList.addAll(videos)
|
||||
}
|
||||
document.select("script:containsData(var videos)").forEach { script ->
|
||||
fetchUrls(script.data()).map { url ->
|
||||
try {
|
||||
serverVideoResolver(url).also(videoList::addAll)
|
||||
} catch (_: Exception) {}
|
||||
}
|
||||
}
|
||||
return videoList
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
override fun videoListSelector() = throw Exception("not used")
|
||||
|
||||
override fun videoUrlParse(document: Document) = throw Exception("not used")
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
|
||||
|
@ -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,103 +147,123 @@ 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 {
|
||||
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")
|
||||
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()
|
||||
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) { }
|
||||
}
|
||||
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)
|
||||
}
|
||||
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)
|
||||
.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)) }
|
||||
}
|
||||
}
|
||||
if (embedUrl.contains("filelions") || embedUrl.contains("lion")) {
|
||||
StreamWishExtractor(client, headers).videosFromUrl(url, videoNameGen = { "$prefix FileLions:$it" }).also(videoList::addAll)
|
||||
}
|
||||
} catch (_: Exception) { }
|
||||
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()
|
||||
}
|
||||
}
|
||||
screen.addPreference(videoQualityPref)
|
||||
}.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()
|
||||
}
|
||||
}.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)
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
screen.addPreference(videoQualityPref)
|
||||
}.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()
|
||||
}
|
||||
}.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)
|
||||
}
|
||||
}
|
||||
|
@ -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"),
|
||||
)
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
screen.addPreference(videoQualityPref)
|
||||
}.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()
|
||||
}
|
||||
}.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)
|
||||
}
|
||||
}
|
||||
|
@ -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'))
|
||||
}
|
||||
|
||||
|
||||
|
@ -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,74 +213,117 @@ 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()
|
||||
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)) }
|
||||
try {
|
||||
if (embedUrl.contains("voe")) {
|
||||
VoeExtractor(client).videoFromUrl(url, prefix = "$prefix Voe:")?.let { videoList.add(it) }
|
||||
}
|
||||
}
|
||||
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) }
|
||||
}
|
||||
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://")
|
||||
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> {
|
||||
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
|
||||
|
@ -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
|
||||
}
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 6.9 KiB |
@ -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()
|
||||
}
|
||||
}
|
||||
screen.addPreference(videoQualityPref)
|
||||
}.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()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
}
|
||||
}
|
||||
|
@ -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'))
|
||||
}
|
||||
|
||||
|
||||
|
@ -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/")
|
||||
|
||||
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) }
|
||||
}
|
||||
try {
|
||||
when {
|
||||
"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()
|
||||
}
|
||||
}
|
||||
screen.addPreference(videoQualityPref)
|
||||
}.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()
|
||||
}
|
||||
}.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)
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ ext {
|
||||
extName = 'LocoPelis'
|
||||
pkgNameSuffix = 'es.locopelis'
|
||||
extClass = '.LocoPelis'
|
||||
extVersionCode = 17
|
||||
extVersionCode = 18
|
||||
libVersion = '13'
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
screen.addPreference(videoQualityPref)
|
||||
}.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()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ ext {
|
||||
extName = 'MetroSeries'
|
||||
pkgNameSuffix = 'es.metroseries'
|
||||
extClass = '.MetroSeries'
|
||||
extVersionCode = 1
|
||||
extVersionCode = 2
|
||||
libVersion = '13'
|
||||
}
|
||||
|
||||
|
@ -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,45 +106,85 @@ 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 objectNumber = document.select("#aa-season").attr("data-object")
|
||||
|
||||
val formBody = FormBody.Builder()
|
||||
.add("action", "action_select_season")
|
||||
.add("season", season)
|
||||
.add("post", post)
|
||||
.add("object", objectNumber)
|
||||
.build()
|
||||
|
||||
val request = Request.Builder()
|
||||
.url("https://metroseries.net/wp-admin/admin-ajax.php")
|
||||
.post(formBody)
|
||||
.header("Origin", baseUrl)
|
||||
.header("Referer", response.request.url.toString())
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.build()
|
||||
val docEpisodes = 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 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)
|
||||
.add("post", post)
|
||||
.add("object", objectNumber)
|
||||
.build()
|
||||
|
||||
val request = Request.Builder()
|
||||
.url("https://metroseries.net/wp-admin/admin-ajax.php")
|
||||
.post(formBody)
|
||||
.header("Origin", baseUrl)
|
||||
.header("Referer", referer)
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.build()
|
||||
val detail = client.newCall(request).execute().asJsoup()
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ ext {
|
||||
extName = 'PelisForte'
|
||||
pkgNameSuffix = 'es.pelisforte'
|
||||
extClass = '.PelisForte'
|
||||
extVersionCode = 3
|
||||
extVersionCode = 4
|
||||
libVersion = '13'
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
|
||||
|
@ -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,68 +163,111 @@ 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")) {
|
||||
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("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))
|
||||
}
|
||||
} 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/")
|
||||
.build()
|
||||
StreamWishExtractor(client, docHeaders).videosFromUrl(url, "StreamWish")
|
||||
} else if (server.contains("filemoon") || server.contains("moonplayer")) {
|
||||
FilemoonExtractor(client).videosFromUrl(url, headers = headers).also(videoList::addAll)
|
||||
}
|
||||
} catch (_: Exception) {}
|
||||
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()
|
||||
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")!!
|
||||
.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
|
||||
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/")
|
||||
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()
|
||||
}
|
||||
}
|
||||
screen.addPreference(videoQualityPref)
|
||||
}.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()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
}
|
||||
}
|
||||
|
@ -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,72 +147,119 @@ 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()
|
||||
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)) }
|
||||
try {
|
||||
if (embedUrl.contains("voe")) {
|
||||
VoeExtractor(client).videoFromUrl(url, prefix = "$prefix Voe:")?.let { videoList.add(it) }
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
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://")
|
||||
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 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()
|
||||
}
|
||||
}
|
||||
screen.addPreference(videoQualityPref)
|
||||
}.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()
|
||||
}
|
||||
}.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)
|
||||
}
|
||||
}
|
||||
|
@ -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,73 +160,117 @@ 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 {
|
||||
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")
|
||||
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()
|
||||
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) { }
|
||||
}
|
||||
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)
|
||||
}
|
||||
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)
|
||||
.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, "Tomatomatela", file, headers = null)) }
|
||||
}
|
||||
}
|
||||
} catch (_: Exception) { }
|
||||
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()
|
||||
}
|
||||
}
|
||||
screen.addPreference(videoQualityPref)
|
||||
}.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()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
}
|
||||
}
|
||||
|