mirror of
https://github.com/yuzono/anime-extensions.git
synced 2026-06-13 05:29:44 +00:00
StreamWish (extractor): refactor & optimize (#419)
* Using helpers * Using suspend functions * lint * improve handling * Fix suspend calling sites * Fix suspend calling sites * Fix suspend calling sites * Fix suspend calling sites
This commit is contained in:
@@ -4,14 +4,19 @@ import aniyomi.lib.playlistutils.PlaylistUtils
|
||||
import eu.kanade.tachiyomi.animesource.model.Track
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import eu.kanade.tachiyomi.network.await
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import keiyoushi.lib.jsunpacker.JsUnpacker
|
||||
import keiyoushi.lib.synchrony.Deobfuscator
|
||||
import keiyoushi.utils.UrlUtils
|
||||
import keiyoushi.utils.bodyString
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import okhttp3.OkHttpClient
|
||||
import org.jsoup.Jsoup
|
||||
|
||||
@@ -26,29 +31,36 @@ class StreamWishExtractor(private val client: OkHttpClient, private val headers:
|
||||
private val mainServersRegex = """main\s*=\s*\[(.*?)]""".toRegex(RegexOption.DOT_MATCHES_ALL)
|
||||
private val rulesServersRegex = """rules\s*=\s*\[(.*?)]""".toRegex(RegexOption.DOT_MATCHES_ALL)
|
||||
|
||||
fun videosFromUrl(url: String, prefix: String) = videosFromUrl(url) { "$prefix - $it" }
|
||||
suspend fun videosFromUrl(url: String, prefix: String) = videosFromUrl(url) { "$prefix - $it" }
|
||||
|
||||
fun videosFromUrl(url: String, videoNameGen: (String) -> String = { quality -> "StreamWish - $quality" }): List<Video> {
|
||||
suspend fun videosFromUrl(url: String, videoNameGen: (String) -> String = { quality -> "StreamWish - $quality" }): List<Video> {
|
||||
val embedUrl = getEmbedUrl(url).toHttpUrl()
|
||||
val id = getEmbedId(url)
|
||||
for (domain in DOMAINS) {
|
||||
val fullUrl = if (id.startsWith("https://")) id else "https://$domain/$id"
|
||||
|
||||
// If id is already an absolute URL, avoid iterating over all DOMAINS and reuse it directly.
|
||||
val isAbsoluteId = id.startsWith("https://") || id.startsWith("http://")
|
||||
val domainsToTry = if (isAbsoluteId) listOf("") else DOMAINS
|
||||
|
||||
for (domain in domainsToTry) {
|
||||
val fullUrl = UrlUtils.fixUrl(id, "https://$domain") ?: continue
|
||||
try {
|
||||
val response = client.newCall(GET(fullUrl, headers)).await()
|
||||
if (!response.isSuccessful) {
|
||||
response.close()
|
||||
continue
|
||||
}
|
||||
|
||||
val response = client.newCall(GET(fullUrl, headers)).execute()
|
||||
if (!response.isSuccessful) continue
|
||||
|
||||
val body = response.body.string()
|
||||
if (body.isEmpty()) continue
|
||||
val body = response.bodyString()
|
||||
if (body.isBlank()) continue
|
||||
var doc = Jsoup.parse(body)
|
||||
|
||||
val scriptElement = doc.selectFirst("body > script[src*=/main.js]")
|
||||
if (scriptElement != null) {
|
||||
val scriptUrl = scriptElement.absUrl("src")
|
||||
val scriptContent = client.newCall(GET(scriptUrl, headers)).execute().body.string()
|
||||
val scriptContent = client.newCall(GET(scriptUrl, headers)).awaitSuccess().bodyString()
|
||||
|
||||
val deobfuscatedScript = runCatching { Deobfuscator.deobfuscateScript(scriptContent) }.getOrNull()
|
||||
?: return emptyList()
|
||||
?: continue
|
||||
|
||||
val dmcaServers = extractServerList(dmcaServersRegex, deobfuscatedScript)
|
||||
|
||||
@@ -60,17 +72,16 @@ class StreamWishExtractor(private val client: OkHttpClient, private val headers:
|
||||
mainServers.randomOrNull()
|
||||
} else {
|
||||
dmcaServers.randomOrNull()
|
||||
} ?: return emptyList()
|
||||
} ?: continue
|
||||
|
||||
val redirectedUrl = embedUrl.newBuilder()
|
||||
.host(destination)
|
||||
.build()
|
||||
.toString()
|
||||
|
||||
doc = client.newCall(GET(getEmbedUrl(redirectedUrl), headers)).execute().asJsoup()
|
||||
doc = client.newCall(GET(getEmbedUrl(redirectedUrl), headers)).awaitSuccess().useAsJsoup()
|
||||
}
|
||||
|
||||
|
||||
val scriptBody = doc.selectFirst("script:containsData(m3u8)")?.data()
|
||||
?.let { script ->
|
||||
if (script.contains("eval(function(p,a,c")) {
|
||||
@@ -89,13 +100,15 @@ class StreamWishExtractor(private val client: OkHttpClient, private val headers:
|
||||
|
||||
return playlistUtils.extractFromHls(
|
||||
playlistUrl = masterUrl,
|
||||
referer = "https://${url.toHttpUrl().host}/",
|
||||
referer = masterUrl.toHttpUrlOrNull()
|
||||
?.let { "${it.scheme}://${it.host}/" }
|
||||
?: "https://${url.toHttpUrl().host}/",
|
||||
videoNameGen = videoNameGen,
|
||||
subtitleList = playlistUtils.fixSubtitles(subtitleList),
|
||||
)
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
if (id.startsWith("https://")) return emptyList()
|
||||
if (isAbsoluteId) return emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,7 +164,7 @@ class StreamWishExtractor(private val client: OkHttpClient, private val headers:
|
||||
private val DOMAINS = listOf(
|
||||
"streamwish.com",
|
||||
"niramirus.com",
|
||||
"medixiru.com"
|
||||
"medixiru.com",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,10 +20,11 @@ import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.await
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import keiyoushi.utils.addListPreference
|
||||
import keiyoushi.utils.getPreferencesLazy
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.parallelMapNotNullBlocking
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import okhttp3.Call
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
@@ -66,7 +67,7 @@ class JavGuru :
|
||||
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/most-watched-rank/", headers)
|
||||
|
||||
override fun popularAnimeParse(response: Response): AnimesPage {
|
||||
popularElements = response.asJsoup().select(".tabcontent li")
|
||||
popularElements = response.useAsJsoup().select(".tabcontent li")
|
||||
|
||||
return cachedPopularAnimeParse(1)
|
||||
}
|
||||
@@ -94,7 +95,7 @@ class JavGuru :
|
||||
}
|
||||
|
||||
override fun latestUpdatesParse(response: Response): AnimesPage {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
|
||||
val entries = document.select("div.site-content div.inside-article:not(:contains(nothing))").map { element ->
|
||||
SAnime.create().apply {
|
||||
@@ -183,7 +184,7 @@ class JavGuru :
|
||||
}
|
||||
|
||||
override fun relatedAnimeListParse(response: Response): List<SAnime> {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
return document.select("div.woo-sc-related-posts li").map { element ->
|
||||
SAnime.create().apply {
|
||||
element.select("a.thumbnail").let { a ->
|
||||
@@ -214,7 +215,7 @@ class JavGuru :
|
||||
override fun searchAnimeParse(response: Response) = latestUpdatesParse(response)
|
||||
|
||||
override fun animeDetailsParse(response: Response): SAnime {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
|
||||
val javId = document.selectFirst(".infoleft li:contains(code)")?.ownText()
|
||||
val siteCover = document.select(".large-screenshot img").attr("abs:src")
|
||||
@@ -250,7 +251,7 @@ class JavGuru :
|
||||
)
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
|
||||
val iframeData = document.selectFirst("script:containsData(iframe_url)")?.html()
|
||||
?: return emptyList()
|
||||
@@ -261,19 +262,19 @@ class JavGuru :
|
||||
.toList()
|
||||
|
||||
return iframeUrls
|
||||
.mapNotNull(::resolveHosterUrl)
|
||||
.parallelMapNotNullBlocking(::resolveHosterUrl)
|
||||
.parallelCatchingFlatMapBlocking(::getVideos)
|
||||
}
|
||||
|
||||
private fun resolveHosterUrl(iframeUrl: String): String? {
|
||||
val iframeResponse = client.newCall(GET(iframeUrl, headers)).execute()
|
||||
private suspend fun resolveHosterUrl(iframeUrl: String): String? = runCatching {
|
||||
val iframeResponse = client.newCall(GET(iframeUrl, headers)).await()
|
||||
|
||||
if (iframeResponse.isSuccessful.not()) {
|
||||
iframeResponse.close()
|
||||
return null
|
||||
}
|
||||
|
||||
val iframeDocument = iframeResponse.asJsoup()
|
||||
val iframeDocument = iframeResponse.useAsJsoup()
|
||||
|
||||
val script = iframeDocument.selectFirst("script:containsData(start_player)")
|
||||
?.html() ?: return null
|
||||
@@ -290,7 +291,7 @@ class JavGuru :
|
||||
.build()
|
||||
|
||||
val redirectUrl = noRedirectClient.newCall(GET(olidUrl, newHeaders))
|
||||
.execute().use { it.header("location") }
|
||||
.await().use { it.header("location") }
|
||||
?: return null
|
||||
|
||||
if (redirectUrl.toHttpUrlOrNull() == null) {
|
||||
@@ -298,7 +299,7 @@ class JavGuru :
|
||||
}
|
||||
|
||||
return redirectUrl
|
||||
}
|
||||
}.getOrNull()
|
||||
|
||||
private val streamWishExtractor by lazy {
|
||||
val swHeaders = headersBuilder()
|
||||
@@ -313,7 +314,7 @@ class JavGuru :
|
||||
private val maxStreamExtractor by lazy { MaxStreamExtractor(client, headers) }
|
||||
private val emTurboExtractor by lazy { EmTurboExtractor(client, headers) }
|
||||
|
||||
private fun getVideos(hosterUrl: String): List<Video> = when {
|
||||
private suspend fun getVideos(hosterUrl: String): List<Video> = when {
|
||||
listOf("javplaya", "javclan").any { it in hosterUrl } -> {
|
||||
streamWishExtractor.videosFromUrl(hosterUrl)
|
||||
}
|
||||
|
||||
@@ -14,10 +14,12 @@ 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.network.GET
|
||||
import eu.kanade.tachiyomi.network.await
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import keiyoushi.utils.bodyString
|
||||
import keiyoushi.utils.getPreferencesLazy
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
@@ -92,7 +94,7 @@ class SupJav(override val lang: String = "en") :
|
||||
}
|
||||
|
||||
private fun searchAnimeByIdParse(response: Response): AnimesPage {
|
||||
val details = animeDetailsParse(response.asJsoup()).apply {
|
||||
val details = animeDetailsParse(response.useAsJsoup()).apply {
|
||||
setUrlWithoutDomain(response.request.url.toString())
|
||||
initialized = true
|
||||
}
|
||||
@@ -141,7 +143,7 @@ class SupJav(override val lang: String = "en") :
|
||||
|
||||
// ============================ Video Links =============================
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val doc = response.asJsoup()
|
||||
val doc = response.useAsJsoup()
|
||||
|
||||
val players = doc.select("div.btnst > a").toList()
|
||||
.filter { it.text() in SUPPORTED_PLAYERS }
|
||||
@@ -163,9 +165,9 @@ class SupJav(override val lang: String = "en") :
|
||||
client.newBuilder().followRedirects(false).build()
|
||||
}
|
||||
|
||||
private fun videosFromPlayer(player: Pair<String, String>): List<Video> {
|
||||
private suspend fun videosFromPlayer(player: Pair<String, String>): List<Video> {
|
||||
val (hoster, id) = player
|
||||
val url = noRedirectClient.newCall(GET("$PROTECTOR_URL/supjav.php?c=$id", protectorHeaders)).execute()
|
||||
val url = noRedirectClient.newCall(GET("$PROTECTOR_URL/supjav.php?c=$id", protectorHeaders)).await()
|
||||
.use { it.headers["location"] }
|
||||
?: return emptyList()
|
||||
|
||||
@@ -177,7 +179,7 @@ class SupJav(override val lang: String = "en") :
|
||||
"FST" -> streamwishExtractor.videosFromUrl(url)
|
||||
|
||||
"TV" -> {
|
||||
val body = client.newCall(GET(url)).execute().body.string()
|
||||
val body = client.newCall(GET(url)).awaitSuccess().bodyString()
|
||||
val playlistUrl = body.substringAfter("var urlPlay = '", "")
|
||||
.substringBefore("';")
|
||||
.takeUnless(String::isEmpty)
|
||||
@@ -206,13 +208,6 @@ class SupJav(override val lang: String = "en") :
|
||||
entryValues = PREF_QUALITY_ENTRIES
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
@@ -13,9 +13,10 @@ 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.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import keiyoushi.utils.getPreferencesLazy
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
@@ -56,7 +57,7 @@ class ArabSeed :
|
||||
override fun episodeListSelector() = "div.ContainerEpisodesList a"
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
val episodes = document.select(episodeListSelector())
|
||||
return when {
|
||||
episodes.isEmpty() -> {
|
||||
@@ -78,28 +79,29 @@ class ArabSeed :
|
||||
|
||||
// ============================ Video Links =============================
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val doc = response.asJsoup()
|
||||
val doc = response.useAsJsoup()
|
||||
val watchUrl = doc.selectFirst("a.watchBTn")!!.attr("href")
|
||||
val element = client.newCall(GET(watchUrl, headers)).execute().asJsoup()
|
||||
val element = client.newCall(GET(watchUrl, headers)).execute().useAsJsoup()
|
||||
return videosFromElement(element)
|
||||
}
|
||||
|
||||
override fun videoListSelector() = "div.containerServers ul li"
|
||||
|
||||
private fun videosFromElement(document: Document): List<Video> = document.select(videoListSelector()).parallelCatchingFlatMapBlocking { element ->
|
||||
val quality = element.text()
|
||||
val embedUrl = element.attr("data-link")
|
||||
getVideosFromUrl(embedUrl, quality)
|
||||
}
|
||||
private fun videosFromElement(document: Document): List<Video> = document.select(videoListSelector())
|
||||
.parallelCatchingFlatMapBlocking { element ->
|
||||
val quality = element.text()
|
||||
val embedUrl = element.attr("data-link")
|
||||
getVideosFromUrl(embedUrl, quality)
|
||||
}
|
||||
|
||||
private val doodExtractor by lazy { DoodExtractor(client) }
|
||||
private val streamwishExtractor by lazy { StreamWishExtractor(client, headers) }
|
||||
private val voeExtractor by lazy { VoeExtractor(client, headers) }
|
||||
|
||||
private fun getVideosFromUrl(url: String, quality: String): List<Video> = when {
|
||||
private suspend fun getVideosFromUrl(url: String, quality: String): List<Video> = when {
|
||||
"reviewtech" in url || "reviewrate" in url -> {
|
||||
val iframeResponse = client.newCall(GET(url)).execute()
|
||||
.asJsoup()
|
||||
val iframeResponse = client.newCall(GET(url)).awaitSuccess()
|
||||
.useAsJsoup()
|
||||
val videoUrl = iframeResponse.selectFirst("source")!!.attr("abs:src")
|
||||
listOf(Video(videoUrl, quality + "p", videoUrl))
|
||||
}
|
||||
@@ -211,13 +213,6 @@ class ArabSeed :
|
||||
entryValues = PREF_QUALITY_VALUES
|
||||
setDefaultValue(PREF_QUALITY_DEFAULT)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}
|
||||
screen.addPreference(videoQualityPref)
|
||||
}
|
||||
|
||||
@@ -15,9 +15,10 @@ import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.network.await
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import keiyoushi.utils.getPreferencesLazy
|
||||
import keiyoushi.utils.parallelCatchingFlatMap
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
@@ -67,10 +68,10 @@ class EgyDead :
|
||||
return episode
|
||||
}
|
||||
fun addEpisodes(res: Response, final: Boolean = false) {
|
||||
val document = res.asJsoup()
|
||||
val document = res.useAsJsoup()
|
||||
val url = res.request.url.toString()
|
||||
if (final) {
|
||||
document.select(episodeListSelector()).map {
|
||||
document.select(episodeListSelector()).forEach {
|
||||
val episode = episodeFromElement(it)
|
||||
val season = document.select("div.infoBox div.singleTitle").text()
|
||||
val seasonTxt = season.substringAfter("الموسم ").substringBefore(" ")
|
||||
@@ -78,16 +79,16 @@ class EgyDead :
|
||||
episodes.add(episode)
|
||||
}
|
||||
} else if (url.contains("assembly")) {
|
||||
document.select("div.salery-list li.movieItem a").map {
|
||||
document.select("div.salery-list li.movieItem a").forEach {
|
||||
episodes.add(episodeExtract(it))
|
||||
}
|
||||
} else if (url.contains("serie") || url.contains("season")) {
|
||||
if (document.select("div.seasons-list li.movieItem a").isNullOrEmpty()) {
|
||||
document.select(episodeListSelector()).map {
|
||||
if (document.select("div.seasons-list li.movieItem a").isEmpty()) {
|
||||
document.select(episodeListSelector()).forEach {
|
||||
episodes.add(episodeFromElement(it))
|
||||
}
|
||||
} else {
|
||||
document.select("div.seasons-list li.movieItem a").map {
|
||||
document.select("div.seasons-list li.movieItem a").forEach {
|
||||
addEpisodes(client.newCall(GET(it.attr("href"))).execute(), true)
|
||||
}
|
||||
}
|
||||
@@ -125,14 +126,14 @@ class EgyDead :
|
||||
|
||||
val document = client.newCall(POST(baseUrl + episode.url, body = requestBody))
|
||||
.await()
|
||||
.asJsoup()
|
||||
.useAsJsoup()
|
||||
return document.select(videoListSelector()).parallelCatchingFlatMap {
|
||||
val url = it.attr("data-link")
|
||||
extractVideos(url)
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractVideos(url: String): List<Video> = when {
|
||||
private suspend fun extractVideos(url: String): List<Video> = when {
|
||||
DOOD_REGEX.containsMatchIn(url) -> {
|
||||
DoodExtractor(client).videoFromUrl(url, "Dood mirror")?.let(::listOf)
|
||||
}
|
||||
@@ -142,7 +143,7 @@ class EgyDead :
|
||||
}
|
||||
|
||||
url.contains("ahvsh") -> {
|
||||
val request = client.newCall(GET(url, headers)).execute().asJsoup()
|
||||
val request = client.newCall(GET(url, headers)).awaitSuccess().useAsJsoup()
|
||||
val script = request.selectFirst("script:containsData(sources)")!!.data()
|
||||
val streamLink = Regex("sources:\\s*\\[\\{\\s*\\t*file:\\s*[\"']([^\"']+)").find(script)!!.groupValues[1]
|
||||
val quality = Regex("'qualityLabels'\\s*:\\s*\\{\\s*\".*?\"\\s*:\\s*\"(.*?)\"").find(script)!!.groupValues[1]
|
||||
@@ -154,7 +155,7 @@ class EgyDead :
|
||||
}
|
||||
|
||||
url.contains("fanakishtuna") -> {
|
||||
val request = client.newCall(GET(url, headers)).execute().asJsoup()
|
||||
val request = client.newCall(GET(url, headers)).awaitSuccess().useAsJsoup()
|
||||
val data = request.selectFirst("script:containsData(sources)")!!.data()
|
||||
val streamLink = Regex("sources:\\s*\\[\\{\\s*\\t*file:\\s*[\"']([^\"']+)").find(data)!!.groupValues[1]
|
||||
listOf(Video(streamLink, "Mirror: High Quality", streamLink))
|
||||
@@ -162,7 +163,7 @@ class EgyDead :
|
||||
|
||||
url.contains("uqload") -> {
|
||||
val newURL = url.replace("https://uqload.co/", "https://www.uqload.co/")
|
||||
val request = client.newCall(GET(newURL, headers)).execute().asJsoup()
|
||||
val request = client.newCall(GET(newURL, headers)).awaitSuccess().useAsJsoup()
|
||||
val data = request.selectFirst("script:containsData(sources)")!!.data()
|
||||
val streamLink = data.substringAfter("sources: [\"").substringBefore("\"]")
|
||||
listOf(Video(streamLink, "Uqload: Mirror", streamLink))
|
||||
@@ -302,13 +303,6 @@ class EgyDead :
|
||||
entryValues = arrayOf("1080", "720", "480", "360", "240", "Dood", "Uqload")
|
||||
setDefaultValue("1080")
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}
|
||||
screen.addPreference(videoQualityPref)
|
||||
}
|
||||
|
||||
@@ -18,13 +18,14 @@ 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.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import keiyoushi.lib.i18n.Intl
|
||||
import keiyoushi.utils.addEditTextPreference
|
||||
import keiyoushi.utils.addListPreference
|
||||
import keiyoushi.utils.delegate
|
||||
import keiyoushi.utils.getPreferencesLazy
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
@@ -76,23 +77,23 @@ class Tuktukcinema :
|
||||
override fun episodeListSelector(): String = "section.allseasonss div.Block--Item"
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
val url = response.request.url.toString()
|
||||
val seasonsDOM = document.select(episodeListSelector())
|
||||
return if (seasonsDOM.isNullOrEmpty()) {
|
||||
return if (seasonsDOM.isEmpty()) {
|
||||
SEpisode.create().apply {
|
||||
setUrlWithoutDomain(url)
|
||||
name = "مشاهدة"
|
||||
}.let(::listOf)
|
||||
} else {
|
||||
val selectedSeason = document.selectFirst("div#mpbreadcrumbs a span:contains(الموسم)")?.text().orEmpty()
|
||||
seasonsDOM.reversed().flatMap { season ->
|
||||
seasonsDOM.reversed().parallelCatchingFlatMapBlocking { season ->
|
||||
val seasonText = season.select("h3").text()
|
||||
val seasonUrl = season.selectFirst("a")?.attr("abs:href") ?: return@flatMap emptyList()
|
||||
val seasonUrl = season.selectFirst("a")?.attr("abs:href") ?: return@parallelCatchingFlatMapBlocking emptyList()
|
||||
val seasonDoc = if (selectedSeason == seasonText) {
|
||||
document
|
||||
} else {
|
||||
client.newCall(GET(seasonUrl)).execute().asJsoup()
|
||||
client.newCall(GET(seasonUrl)).awaitSuccess().useAsJsoup()
|
||||
}
|
||||
val seasonNum = if (seasonsDOM.size == 1) "1" else seasonText.filter { it.isDigit() }.ifEmpty { "0" }
|
||||
seasonDoc.select("section.allepcont a").mapIndexed { index, episode ->
|
||||
@@ -115,7 +116,7 @@ class Tuktukcinema :
|
||||
override fun videoListSelector(): String = "ul li.server--item"
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
return document.select(videoListSelector()).parallelCatchingFlatMapBlocking {
|
||||
val url = it.attr("data-link").substringBefore("0REL0Y").reversed()
|
||||
extractVideos(String(Base64.decode(url, Base64.DEFAULT), Charsets.UTF_8), it.text())
|
||||
@@ -129,7 +130,7 @@ class Tuktukcinema :
|
||||
private val vidBomExtractor by lazy { VidBomExtractor(client) }
|
||||
private val vidLandExtractor by lazy { VidLandExtractor(client) }
|
||||
|
||||
private fun extractVideos(
|
||||
private suspend fun extractVideos(
|
||||
url: String,
|
||||
server: String,
|
||||
customQuality: String? = null,
|
||||
@@ -153,7 +154,7 @@ class Tuktukcinema :
|
||||
}
|
||||
|
||||
"krakenfiles" in server -> {
|
||||
val page = client.newCall(GET(url, headers)).execute().asJsoup()
|
||||
val page = client.newCall(GET(url, headers)).awaitSuccess().useAsJsoup()
|
||||
page.select("source").map {
|
||||
Video(it.attr("src"), "Kraken" + customQuality?.let { q -> ": $q" }.orEmpty(), it.attr("src"))
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@ import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import keiyoushi.utils.getPreferencesLazy
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
@@ -65,7 +65,7 @@ class AnimeBase :
|
||||
|
||||
private val searchToken by lazy {
|
||||
client.newCall(GET("$baseUrl/searching", headers)).execute()
|
||||
.asJsoup()
|
||||
.useAsJsoup()
|
||||
.selectFirst("form > input[name=_token]")!!
|
||||
.attr("value")
|
||||
}
|
||||
@@ -94,7 +94,7 @@ class AnimeBase :
|
||||
}
|
||||
|
||||
override fun searchAnimeParse(response: Response): AnimesPage {
|
||||
val doc = response.asJsoup()
|
||||
val doc = response.useAsJsoup()
|
||||
|
||||
return when {
|
||||
doc.location().contains("/searching") -> {
|
||||
@@ -138,7 +138,7 @@ class AnimeBase :
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseStatus(status: String?) = when (status?.orEmpty()) {
|
||||
private fun parseStatus(status: String?) = when (status.orEmpty()) {
|
||||
"Laufend" -> SAnime.ONGOING
|
||||
"Abgeschlossen" -> SAnime.COMPLETED
|
||||
else -> SAnime.UNKNOWN
|
||||
@@ -183,7 +183,7 @@ class AnimeBase :
|
||||
}
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val doc = response.asJsoup()
|
||||
val doc = response.useAsJsoup()
|
||||
val selector = response.request.url.queryParameter("selector")
|
||||
?: return emptyList()
|
||||
|
||||
@@ -214,8 +214,8 @@ class AnimeBase :
|
||||
private val unpackerExtractor by lazy { UnpackerExtractor(client, headers) }
|
||||
private val vidguardExtractor by lazy { VidGuardExtractor(client) }
|
||||
|
||||
private fun getVideosFromHoster(hoster: String, urlpart: String): List<Video> {
|
||||
val url = hosterSettings.get(hoster)!! + urlpart
|
||||
private suspend fun getVideosFromHoster(hoster: String, urlpart: String): List<Video> {
|
||||
val url = hosterSettings[hoster]!! + urlpart
|
||||
return when (hoster) {
|
||||
"Streamwish" -> streamWishExtractor.videosFromUrl(url)
|
||||
"Voe.SX" -> voeExtractor.videosFromUrl(url)
|
||||
@@ -253,13 +253,6 @@ class AnimeBase :
|
||||
entryValues = PREF_LANG_VALUES
|
||||
setDefaultValue(PREF_LANG_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 {
|
||||
@@ -269,13 +262,6 @@ class AnimeBase :
|
||||
entryValues = PREF_QUALITY_VALUES
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
@@ -11,9 +11,10 @@ import aniyomi.lib.streamwishextractor.StreamWishExtractor
|
||||
import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.multisrc.dooplay.DooPlay
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.await
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import keiyoushi.utils.bodyString
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
@@ -44,7 +45,7 @@ class Cinemathek :
|
||||
|
||||
// ============================ Video Links =============================
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val players = response.asJsoup().select("ul#playeroptionsul li")
|
||||
val players = response.useAsJsoup().select("ul#playeroptionsul li")
|
||||
val hosterSelection = preferences.getStringSet(PREF_HOSTER_SELECTION_KEY, PREF_HOSTER_SELECTION_DEFAULT)!!
|
||||
return players.parallelCatchingFlatMapBlocking { player ->
|
||||
val url = getPlayerUrl(player).takeUnless(String::isEmpty)!!
|
||||
@@ -58,8 +59,8 @@ class Cinemathek :
|
||||
val num = player.attr("data-nume")
|
||||
if (num == "trailer") return ""
|
||||
return client.newCall(GET("$baseUrl/wp-json/dooplayer/v2/$id/$type/$num"))
|
||||
.await()
|
||||
.body.string()
|
||||
.awaitSuccess()
|
||||
.bodyString()
|
||||
.substringAfter("\"embed_url\":\"")
|
||||
.substringBefore("\",")
|
||||
.replace("\\", "")
|
||||
@@ -71,7 +72,7 @@ class Cinemathek :
|
||||
private val streamtapeExtractor by lazy { StreamTapeExtractor(client) }
|
||||
private val streamwishExtractor by lazy { StreamWishExtractor(client, headers) }
|
||||
|
||||
private fun getPlayerVideos(url: String, hosterSelection: Set<String>): List<Video> = when {
|
||||
private suspend fun getPlayerVideos(url: String, hosterSelection: Set<String>): List<Video> = when {
|
||||
url.contains("https://streamlare.com") && hosterSelection.contains("slare") -> {
|
||||
streamlareExtractor.videosFromUrl(url)
|
||||
}
|
||||
|
||||
@@ -22,9 +22,10 @@ import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import keiyoushi.utils.getPreferencesLazy
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.tryParse
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Response
|
||||
import org.jsoup.Jsoup
|
||||
@@ -98,7 +99,7 @@ class Einfach :
|
||||
}
|
||||
|
||||
private fun searchAnimeByPathParse(response: Response): AnimesPage {
|
||||
val details = animeDetailsParse(response.asJsoup()).apply {
|
||||
val details = animeDetailsParse(response.useAsJsoup()).apply {
|
||||
setUrlWithoutDomain(response.request.url.toString())
|
||||
initialized = true
|
||||
}
|
||||
@@ -161,12 +162,12 @@ class Einfach :
|
||||
episode_number = eplnum.substringAfterLast(" ").toFloatOrNull() ?: 1F
|
||||
|
||||
name = eplnum.ifBlank { "S1 EP 1" } + " - " + element.selectFirst(".epl-title")?.text().orEmpty()
|
||||
date_upload = element.selectFirst(".epl-date")?.text().orEmpty().toDate()
|
||||
date_upload = element.selectFirst(".epl-date")?.text().let(DATE_FORMATTER::tryParse)
|
||||
}
|
||||
|
||||
// ============================ Video Links =============================
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val doc = response.asJsoup()
|
||||
val doc = response.useAsJsoup()
|
||||
|
||||
val selection = preferences.getStringSet(PREF_HOSTER_SELECTION_KEY, PREF_HOSTER_SELECTION_DEFAULT)!!
|
||||
|
||||
@@ -206,7 +207,7 @@ class Einfach :
|
||||
private val vidozaExtractor by lazy { VidozaExtractor(client) }
|
||||
private val voeExtractor by lazy { VoeExtractor(client, headers) }
|
||||
|
||||
private fun getVideosFromUrl(name: String, url: String): List<Video> = when (name) {
|
||||
private suspend fun getVideosFromUrl(name: String, url: String): List<Video> = when (name) {
|
||||
"doodstream" -> doodExtractor.videosFromUrl(url)
|
||||
"filelions" -> streamwishExtractor.videosFromUrl(url, videoNameGen = { "FileLions - $it" })
|
||||
"filemoon" -> filemoonExtractor.videosFromUrl(url)
|
||||
@@ -233,13 +234,6 @@ class Einfach :
|
||||
entryValues = PREF_QUALITY_VALUES
|
||||
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)
|
||||
|
||||
MultiSelectListPreference(screen.context).apply {
|
||||
@@ -248,18 +242,10 @@ class Einfach :
|
||||
entries = PREF_HOSTER_SELECTION_ENTRIES
|
||||
entryValues = PREF_HOSTER_SELECTION_VALUES
|
||||
setDefaultValue(PREF_HOSTER_SELECTION_DEFAULT)
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
preferences.edit().putStringSet(key, newValue as Set<String>).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
}
|
||||
|
||||
// ============================= Utilities ==============================
|
||||
private fun String.toDate(): Long = runCatching { DATE_FORMATTER.parse(trim())?.time }
|
||||
.getOrNull() ?: 0L
|
||||
|
||||
override fun List<Video>.sort(): List<Video> {
|
||||
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
|
||||
|
||||
|
||||
@@ -25,12 +25,10 @@ import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import keiyoushi.utils.getPreferencesLazy
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.parseAs
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Headers
|
||||
import okhttp3.Response
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class MoflixStream :
|
||||
AnimeHttpSource(),
|
||||
@@ -48,8 +46,6 @@ class MoflixStream :
|
||||
|
||||
private val preferences by getPreferencesLazy()
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
private val apiUrl = "$baseUrl/api/v1"
|
||||
|
||||
// ============================== Popular ===============================
|
||||
@@ -62,7 +58,7 @@ class MoflixStream :
|
||||
val pagination = response.parseAs<PopularPaginationDto>().pagination
|
||||
|
||||
val animeList = pagination.data.parseItems()
|
||||
val hasNextPage = pagination.current_page < pagination.next_page ?: 1
|
||||
val hasNextPage = pagination.current_page < (pagination.next_page ?: 1)
|
||||
return AnimesPage(animeList, hasNextPage)
|
||||
}
|
||||
|
||||
@@ -100,9 +96,7 @@ class MoflixStream :
|
||||
val id = data.title.id
|
||||
val seasonsUrl = "$apiUrl/titles/$id/seasons"
|
||||
|
||||
val seasons = data.seasons
|
||||
|
||||
return when (seasons) {
|
||||
return when (val seasons = data.seasons) {
|
||||
null -> {
|
||||
SEpisode.create().apply {
|
||||
name = "Film"
|
||||
@@ -157,24 +151,24 @@ class MoflixStream :
|
||||
val selection = preferences.getStringSet(PREF_HOSTER_SELECTION_KEY, PREF_HOSTER_SELECTION_DEFAULT)!!
|
||||
val data = response.parseAs<VideoResponseDto>().run { episode ?: title }
|
||||
|
||||
return data!!.videos.flatMap { video ->
|
||||
return data!!.videos.parallelCatchingFlatMapBlocking { video ->
|
||||
val name = video.name
|
||||
val url = video.src
|
||||
runCatching { getVideosFromUrl(url, name, selection) }.getOrElse { emptyList() }
|
||||
}.ifEmpty { throw Exception("No videos!") }
|
||||
getVideosFromUrl(url, name, selection)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getVideosFromUrl(url: String, name: String, selection: Set<String>): List<Video> = when {
|
||||
private suspend fun getVideosFromUrl(url: String, name: String, selection: Set<String>): List<Video> = when {
|
||||
name.contains("Streamtape") && selection.contains("stape") -> {
|
||||
streamtapeExtractor.videoFromUrl(url)?.let(::listOf) ?: emptyList()
|
||||
}
|
||||
|
||||
name.contains("Streamvid") && selection.contains("svid") -> {
|
||||
runBlocking { vidHideExtractor.videosFromUrl(url) }
|
||||
vidHideExtractor.videosFromUrl(url)
|
||||
}
|
||||
|
||||
name.contains("Highstream") && selection.contains("hstream") -> {
|
||||
runBlocking { vidHideExtractor.videosFromUrl(url) { "Highstream - $it" } }
|
||||
vidHideExtractor.videosFromUrl(url) { "Highstream - $it" }
|
||||
}
|
||||
|
||||
name.contains("VidGuard") && selection.contains("vidg") -> {
|
||||
@@ -209,13 +203,6 @@ class MoflixStream :
|
||||
entryValues = PREF_HOSTER_VALUES
|
||||
setDefaultValue(PREF_HOSTER_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)
|
||||
|
||||
MultiSelectListPreference(screen.context).apply {
|
||||
@@ -224,11 +211,6 @@ class MoflixStream :
|
||||
entries = PREF_HOSTER_SELECTION_ENTRIES
|
||||
entryValues = PREF_HOSTER_SELECTION_ENTRIES
|
||||
setDefaultValue(PREF_HOSTER_SELECTION_DEFAULT)
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
preferences.edit().putStringSet(key, newValue as Set<String>).commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
package eu.kanade.tachiyomi.animeextension.es.animeid
|
||||
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.PreferenceScreen
|
||||
import aniyomi.lib.streamtapeextractor.StreamTapeExtractor
|
||||
import aniyomi.lib.streamwishextractor.StreamWishExtractor
|
||||
import aniyomi.lib.universalextractor.UniversalExtractor
|
||||
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.SAnime
|
||||
@@ -13,9 +10,11 @@ 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.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import keiyoushi.utils.getPreferencesLazy
|
||||
import kotlinx.serialization.json.Json
|
||||
import keiyoushi.utils.bodyString
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.parseAs
|
||||
import keiyoushi.utils.tryParse
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.jsonArray
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
@@ -24,13 +23,11 @@ import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Calendar
|
||||
import java.util.Locale
|
||||
|
||||
class AnimeID :
|
||||
ParsedAnimeHttpSource(),
|
||||
ConfigurableAnimeSource {
|
||||
class AnimeID : ParsedAnimeHttpSource() {
|
||||
|
||||
override val name = "AnimeID"
|
||||
|
||||
@@ -40,10 +37,6 @@ class AnimeID :
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
private val preferences by getPreferencesLazy()
|
||||
|
||||
override fun popularAnimeSelector(): String = "#result article.item"
|
||||
|
||||
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/series?sort=views&pag=$page")
|
||||
@@ -62,7 +55,7 @@ class AnimeID :
|
||||
override fun episodeListSelector() = throw UnsupportedOperationException()
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
val animeId = document.select("#ord").attr("data-id")
|
||||
return episodeJsonParse(response.request.url.toString(), animeId)
|
||||
}
|
||||
@@ -80,35 +73,35 @@ class AnimeID :
|
||||
.build()
|
||||
|
||||
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)
|
||||
.execute().bodyString().substringAfter("<body>").substringBefore("</body>")
|
||||
val jObject = responseString.parseAs<JsonObject>()
|
||||
val listCaps = jObject["list"]!!.jsonArray
|
||||
listCaps!!.forEach { cap ->
|
||||
listCaps.forEach { cap ->
|
||||
val capParsed = cap.jsonObject
|
||||
val epNum = capParsed["numero"]!!.jsonPrimitive.content
|
||||
val episode = SEpisode.create()
|
||||
val dateUpload = manualDateParse(capParsed["date"]!!.jsonPrimitive.content!!.toString())
|
||||
val dateUpload = manualDateParse(capParsed["date"]!!.jsonPrimitive.content)
|
||||
episode.episode_number = epNum.toFloat()
|
||||
episode.name = "Episodio $epNum"
|
||||
dateUpload!!.also { episode.date_upload = it }
|
||||
episode.setUrlWithoutDomain(baseUrl + capParsed["href"]!!.jsonPrimitive.content!!.toString())
|
||||
episode.setUrlWithoutDomain(baseUrl + capParsed["href"]!!.jsonPrimitive.content)
|
||||
capList.add(episode)
|
||||
}
|
||||
if (listCaps!!.any()) nextPage += 1 else nextPage = -1
|
||||
if (listCaps.any()) nextPage += 1 else nextPage = -1
|
||||
} while (nextPage != -1)
|
||||
return capList
|
||||
}
|
||||
|
||||
private fun manualDateParse(stringDate: String): Long? = try {
|
||||
val format = SimpleDateFormat("dd MMM yyyy")
|
||||
format.parse(stringDate!!.toString()).time
|
||||
} catch (e: Exception) {
|
||||
var dateParsed = stringDate.split(" ")
|
||||
val arrMonths = arrayOf("Jun", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec")
|
||||
val day = dateParsed[0]!!.trim().toInt()
|
||||
val format = SimpleDateFormat("dd MMM yyyy", Locale.ENGLISH)
|
||||
format.tryParse(stringDate)
|
||||
} catch (_: Exception) {
|
||||
val dateParsed = stringDate.split(" ")
|
||||
val arrMonths = arrayOf("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec")
|
||||
val day = dateParsed[0].trim().toInt()
|
||||
val month = arrMonths.indexOf(dateParsed[1].trim()) + 1
|
||||
val year = dateParsed[2]!!.trim().toInt()
|
||||
Date(year, month, day).time
|
||||
val year = dateParsed[2].trim().toInt()
|
||||
Calendar.getInstance().apply { set(year, month - 1, day) }.time.time
|
||||
}
|
||||
|
||||
override fun episodeFromElement(element: Element) = throw UnsupportedOperationException()
|
||||
@@ -119,9 +112,8 @@ class AnimeID :
|
||||
private val universalExtractor by lazy { UniversalExtractor(client) }
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
val videoList = mutableListOf<Video>()
|
||||
document.select("#partes div.container li.subtab div.parte").forEach { script ->
|
||||
val document = response.useAsJsoup()
|
||||
return document.select("#partes div.container li.subtab div.parte").parallelCatchingFlatMapBlocking { script ->
|
||||
val jsonString = script.attr("data")
|
||||
val titleServer = script.closest(".subtab")?.attr("data-tab-id")?.let {
|
||||
document.selectFirst("#mirrors [data-tab-id=\"$it\"]")?.ownText()?.trim()
|
||||
@@ -129,14 +121,12 @@ class AnimeID :
|
||||
val jsonUnescape = unescapeJava(jsonString).replace("\\", "")
|
||||
val url = fetchUrls(jsonUnescape).firstOrNull()?.replace("\\\\", "\\") ?: ""
|
||||
val matched = conventions.firstOrNull { (_, names) -> names.any { it.lowercase() in url.lowercase() || it.lowercase() in titleServer.lowercase() } }?.first
|
||||
val videos = when (matched) {
|
||||
when (matched) {
|
||||
"streamtape" -> streamtapeExtractor.videosFromUrl(url)
|
||||
"streamwish" -> streamwishExtractor.videosFromUrl(url, videoNameGen = { "StreamWish:$it" })
|
||||
else -> universalExtractor.videosFromUrl(url, headers)
|
||||
}
|
||||
videoList.addAll(videos)
|
||||
}
|
||||
return videoList
|
||||
}
|
||||
|
||||
private val conventions = listOf(
|
||||
@@ -162,7 +152,7 @@ class AnimeID :
|
||||
|
||||
private fun fetchUrls(text: String?): List<String> {
|
||||
if (text.isNullOrEmpty()) return listOf()
|
||||
val linkRegex = "(http|ftp|https):\\/\\/([\\w_-]+(?:(?:\\.[\\w_-]+)+))([\\w.,@?^=%&:\\/~+#-]*[\\w@?^=%&\\/~+#-])".toRegex()
|
||||
val linkRegex = "(http|ftp|https)://([\\w_-]+(?:\\.[\\w_-]+)+)([\\w.,@?^=%&:/~+#-]*[\\w@?^=%&/~+#-])".toRegex()
|
||||
return linkRegex.findAll(text).map { it.value.trim().removeSurrounding("\"") }.toList()
|
||||
}
|
||||
|
||||
@@ -362,22 +352,4 @@ class AnimeID :
|
||||
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/series?sort=newest&pag=$page")
|
||||
|
||||
override fun latestUpdatesSelector() = popularAnimeSelector()
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
ListPreference(screen.context).apply {
|
||||
key = "preferred_quality"
|
||||
title = "Preferred server"
|
||||
entries = arrayOf("StreamTape")
|
||||
entryValues = arrayOf("StreamTape")
|
||||
setDefaultValue("StreamTape")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,10 @@ import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.multisrc.dooplay.DooPlay
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import keiyoushi.utils.bodyString
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
@@ -38,16 +41,14 @@ class Animenix :
|
||||
override val episodeMovieText = "Película"
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val players = response.asJsoup().select("li.dooplay_player_option")
|
||||
return players.flatMap { player ->
|
||||
runCatching {
|
||||
val link = getPlayerUrl(player)
|
||||
getPlayerVideos(link)
|
||||
}.getOrElse { emptyList() }
|
||||
val players = response.useAsJsoup().select("li.dooplay_player_option")
|
||||
return players.parallelCatchingFlatMapBlocking { player ->
|
||||
val link = getPlayerUrl(player)
|
||||
getPlayerVideos(link)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPlayerUrl(player: Element): String {
|
||||
private suspend fun getPlayerUrl(player: Element): String {
|
||||
val body = FormBody.Builder()
|
||||
.add("action", "doo_player_ajax")
|
||||
.add("post", player.attr("data-post"))
|
||||
@@ -55,23 +56,21 @@ class Animenix :
|
||||
.add("type", player.attr("data-type"))
|
||||
.build()
|
||||
return client.newCall(POST("$baseUrl/wp-admin/admin-ajax.php", headers, body))
|
||||
.execute()
|
||||
.let { response ->
|
||||
response.body.string()
|
||||
.substringAfter("\"embed_url\":\"")
|
||||
.substringBefore("\",")
|
||||
.replace("\\", "")
|
||||
}
|
||||
.awaitSuccess().bodyString()
|
||||
.substringAfter("\"embed_url\":\"")
|
||||
.substringBefore("\",")
|
||||
.replace("\\", "")
|
||||
}
|
||||
|
||||
private val filemoonExtractor by lazy { FilemoonExtractor(client) }
|
||||
private val streamWishExtractor by lazy { StreamWishExtractor(headers = headers, client = client) }
|
||||
private val universalExtractor by lazy { UniversalExtractor(client) }
|
||||
|
||||
private fun getPlayerVideos(link: String): List<Video> = when {
|
||||
private suspend fun getPlayerVideos(link: String): List<Video> = when {
|
||||
link.contains("filemoon") -> filemoonExtractor.videosFromUrl(link)
|
||||
link.contains("swdyu") -> streamWishExtractor.videosFromUrl(link)
|
||||
link.contains("wishembed") || link.contains("cdnwish") || link.contains("flaswish") || link.contains("sfastwish") || link.contains("streamwish") || link.contains("asnwish") -> streamWishExtractor.videosFromUrl(link)
|
||||
link.contains("wishembed") || link.contains("cdnwish") || link.contains("flaswish") || link.contains("sfastwish") || link.contains("streamwish") || link.contains("asnwish") ->
|
||||
streamWishExtractor.videosFromUrl(link)
|
||||
else -> universalExtractor.videosFromUrl(link, headers)
|
||||
}
|
||||
|
||||
@@ -164,13 +163,6 @@ class Animenix :
|
||||
entryValues = PREF_LANG_VALUES
|
||||
setDefaultValue(PREF_LANG_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()
|
||||
}
|
||||
}
|
||||
|
||||
val vrfIterceptPref = CheckBoxPreference(screen.context).apply {
|
||||
@@ -185,8 +177,6 @@ class Animenix :
|
||||
}
|
||||
|
||||
// ============================= Utilities ==============================
|
||||
override fun String.toDate() = 0L
|
||||
|
||||
override fun List<Video>.sort(): List<Video> {
|
||||
val quality = preferences.getString(prefQualityKey, prefQualityDefault)!!
|
||||
val lang = preferences.getString(PREF_LANG_KEY, PREF_LANG_DEFAULT)!!
|
||||
|
||||
@@ -19,9 +19,10 @@ 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.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import keiyoushi.utils.getPreferencesLazy
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
@@ -67,14 +68,14 @@ class HomeCine :
|
||||
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/cartelera-series/page/$page", headers)
|
||||
|
||||
override fun popularAnimeParse(response: Response): AnimesPage {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
val elements = document.select(".post")
|
||||
val nextPage = document.select(".nav-links .current ~ a").any()
|
||||
val animeList = elements.map { element ->
|
||||
SAnime.create().apply {
|
||||
setUrlWithoutDomain(element.selectFirst(".lnk-blk")?.attr("abs:href") ?: "")
|
||||
title = element.selectFirst(".entry-header .entry-title")?.text() ?: ""
|
||||
description = element.select(".entry-content p").text() ?: ""
|
||||
description = element.select(".entry-content p").text()
|
||||
thumbnail_url = element.selectFirst(".post-thumbnail figure img")?.let { getImageUrl(it) }
|
||||
}
|
||||
}
|
||||
@@ -90,7 +91,7 @@ class HomeCine :
|
||||
override fun searchAnimeParse(response: Response) = popularAnimeParse(response)
|
||||
|
||||
override fun animeDetailsParse(response: Response): SAnime {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
return SAnime.create().apply {
|
||||
title = document.selectFirst("aside .entry-header .entry-title")?.text() ?: ""
|
||||
description = document.select("aside .description p:not([class])").joinToString { it.text() }
|
||||
@@ -107,7 +108,7 @@ class HomeCine :
|
||||
}
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
val referer = response.request.url.toString()
|
||||
return if (referer.contains("pelicula")) {
|
||||
listOf(
|
||||
@@ -131,7 +132,7 @@ class HomeCine :
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDetailSeason(element: Element, referer: String): List<SEpisode> = try {
|
||||
private suspend fun getDetailSeason(element: Element, referer: String): List<SEpisode> {
|
||||
val post = element.attr("data-post")
|
||||
val season = element.attr("data-season")
|
||||
val formBody = FormBody.Builder()
|
||||
@@ -147,9 +148,9 @@ class HomeCine :
|
||||
.header("Referer", referer)
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.build()
|
||||
val detail = client.newCall(request).execute().asJsoup()
|
||||
val detail = client.newCall(request).awaitSuccess().useAsJsoup()
|
||||
|
||||
detail.select(".post").reversed().mapIndexed { idx, it ->
|
||||
return detail.select(".post").reversed().mapIndexed { idx, it ->
|
||||
val epNumber = try {
|
||||
it.select(".entry-header .num-epi").text().substringAfter("x").substringBefore("–").trim()
|
||||
} catch (_: Exception) {
|
||||
@@ -162,62 +163,65 @@ class HomeCine :
|
||||
episode_number = epNumber.toFloat()
|
||||
}
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
emptyList()
|
||||
}
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
val videoList = mutableListOf<Video>()
|
||||
document.select(".aa-tbs-video a").forEach {
|
||||
val prefix = runCatching {
|
||||
val lang = it.select(".server").text().lowercase()
|
||||
when {
|
||||
lang.contains("latino") -> "[LAT]"
|
||||
lang.contains("castellano") -> "[CAST]"
|
||||
lang.contains("sub") || lang.contains("vose") -> "[SUB]"
|
||||
else -> ""
|
||||
}
|
||||
}.getOrDefault("")
|
||||
val document = response.useAsJsoup()
|
||||
return document.select(".aa-tbs-video a").parallelCatchingFlatMapBlocking {
|
||||
val lang = it.select(".server").text().lowercase()
|
||||
val prefix = when {
|
||||
lang.contains("latino") -> "[LAT]"
|
||||
lang.contains("castellano") -> "[CAST]"
|
||||
lang.contains("sub") || lang.contains("vose") -> "[SUB]"
|
||||
else -> ""
|
||||
}
|
||||
|
||||
val ide = it.attr("href")
|
||||
var src = document.select("$ide iframe").attr("data-src").replace("#038;", "&").replace("&", "")
|
||||
try {
|
||||
if (src.contains("home")) {
|
||||
src = client.newCall(GET(src)).execute().asJsoup().selectFirst("iframe")?.attr("src") ?: ""
|
||||
}
|
||||
if (src.contains("home")) {
|
||||
src = client.newCall(GET(src)).awaitSuccess().useAsJsoup().selectFirst("iframe")?.attr("src") ?: ""
|
||||
}
|
||||
|
||||
if (src.contains("fastream")) {
|
||||
when {
|
||||
src.contains("fastream") -> {
|
||||
if (src.contains("emb.html")) {
|
||||
val key = src.split("/").last()
|
||||
src = "https://fastream.to/embed-$key.html"
|
||||
}
|
||||
FastreamExtractor(client, headers).videosFromUrl(src, needsSleep = false, prefix = "$prefix Fastream:").also(videoList::addAll)
|
||||
FastreamExtractor(client, headers).videosFromUrl(src, needsSleep = false, prefix = "$prefix Fastream:")
|
||||
}
|
||||
if (src.contains("upstream")) {
|
||||
UpstreamExtractor(client).videosFromUrl(src, prefix = "$prefix ").let { videoList.addAll(it) }
|
||||
|
||||
src.contains("upstream") -> {
|
||||
UpstreamExtractor(client).videosFromUrl(src, prefix = "$prefix ")
|
||||
}
|
||||
if (src.contains("yourupload")) {
|
||||
YourUploadExtractor(client).videoFromUrl(src, headers, prefix = "$prefix ").let { videoList.addAll(it) }
|
||||
|
||||
src.contains("yourupload") -> {
|
||||
YourUploadExtractor(client).videoFromUrl(src, headers, prefix = "$prefix ")
|
||||
}
|
||||
if (src.contains("voe")) {
|
||||
VoeExtractor(client, headers).videosFromUrl(src, prefix = "$prefix ").also(videoList::addAll)
|
||||
|
||||
src.contains("voe") -> {
|
||||
VoeExtractor(client, headers).videosFromUrl(src, prefix = "$prefix ")
|
||||
}
|
||||
if (src.contains("wishembed") || src.contains("streamwish") || src.contains("wish")) {
|
||||
StreamWishExtractor(client, headers).videosFromUrl(src) { "$prefix StreamWish:$it" }.also(videoList::addAll)
|
||||
|
||||
src.contains("wishembed") || src.contains("streamwish") || src.contains("wish") -> {
|
||||
StreamWishExtractor(client, headers).videosFromUrl(src) { "$prefix StreamWish:$it" }
|
||||
}
|
||||
if (src.contains("mp4upload")) {
|
||||
Mp4uploadExtractor(client).videosFromUrl(src, headers, prefix = "$prefix ").let { videoList.addAll(it) }
|
||||
|
||||
src.contains("mp4upload") -> {
|
||||
Mp4uploadExtractor(client).videosFromUrl(src, headers, prefix = "$prefix ")
|
||||
}
|
||||
if (src.contains("burst")) {
|
||||
BurstCloudExtractor(client).videoFromUrl(src, headers = headers, prefix = "$prefix ").let { videoList.addAll(it) }
|
||||
|
||||
src.contains("burst") -> {
|
||||
BurstCloudExtractor(client).videoFromUrl(src, headers = headers, prefix = "$prefix ")
|
||||
}
|
||||
if (src.contains("filemoon") || src.contains("moonplayer")) {
|
||||
FilemoonExtractor(client).videosFromUrl(src, headers = headers, prefix = "$prefix Filemoon:").let { videoList.addAll(it) }
|
||||
|
||||
src.contains("filemoon") || src.contains("moonplayer") -> {
|
||||
FilemoonExtractor(client).videosFromUrl(src, headers = headers, prefix = "$prefix Filemoon:")
|
||||
}
|
||||
} catch (_: Exception) {}
|
||||
|
||||
else -> emptyList()
|
||||
}
|
||||
}
|
||||
return videoList
|
||||
}
|
||||
|
||||
override fun List<Video>.sort(): List<Video> {
|
||||
@@ -242,13 +246,6 @@ class HomeCine :
|
||||
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 {
|
||||
@@ -258,13 +255,6 @@ class HomeCine :
|
||||
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 {
|
||||
@@ -274,13 +264,6 @@ class HomeCine :
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,14 +22,16 @@ import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import keiyoushi.lib.cryptoaes.CryptoAES
|
||||
import keiyoushi.utils.addEditTextPreference
|
||||
import keiyoushi.utils.addListPreference
|
||||
import keiyoushi.utils.delegate
|
||||
import keiyoushi.utils.getPreferencesLazy
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.parseAs
|
||||
import kotlinx.serialization.json.Json
|
||||
import keiyoushi.utils.tryParse
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
@@ -51,8 +53,6 @@ class Katanime :
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
private val json = Json { ignoreUnknownKeys = true }
|
||||
|
||||
private val preferences by getPreferencesLazy()
|
||||
|
||||
private val SharedPreferences.userAgent by preferences.delegate(PREF_USER_AGENT, DEFAULT_USER_AGENT)
|
||||
@@ -91,7 +91,7 @@ class Katanime :
|
||||
}
|
||||
|
||||
override fun animeDetailsParse(response: Response): SAnime {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
return SAnime.create().apply {
|
||||
title = document.selectFirst(".comics-title")?.ownText() ?: ""
|
||||
description = document.selectFirst("#sinopsis p")?.ownText()
|
||||
@@ -109,7 +109,7 @@ class Katanime :
|
||||
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/populares", headers)
|
||||
|
||||
override fun popularAnimeParse(response: Response): AnimesPage {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
val elements = document.select("#article-div .full > a")
|
||||
val nextPage = document.select(".pagination .active ~ li:not(.disabled)").any()
|
||||
val animeList = elements.map { element ->
|
||||
@@ -141,7 +141,7 @@ class Katanime :
|
||||
override fun searchAnimeParse(response: Response) = popularAnimeParse(response)
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val jsoup = response.asJsoup()
|
||||
val jsoup = response.useAsJsoup()
|
||||
val paginationElement = jsoup.selectFirst("._pagination") ?: return emptyList()
|
||||
val paginationUrl = paginationElement.attr("abs:data-url")
|
||||
.ifBlank { paginationElement.attr("data-url") }
|
||||
@@ -178,7 +178,7 @@ class Katanime :
|
||||
|
||||
val detailResponse = client.newCall(
|
||||
POST(paginationUrl, headers, body = formBody),
|
||||
).execute().parseAs<EpisodeList>(json)
|
||||
).execute().parseAs<EpisodeList>()
|
||||
|
||||
val pages = detailResponse.ep?.lastPage
|
||||
?: ((detailResponse.ep?.total?.toDouble() ?: 1.0) / (detailResponse.ep?.perPage?.toDouble() ?: Int.MAX_VALUE.toDouble())).ceilPage()
|
||||
@@ -189,7 +189,7 @@ class Katanime :
|
||||
SEpisode.create().apply {
|
||||
name = ep.numero?.let { "Episodio $it" } ?: "Episodio"
|
||||
episode_number = ep.numero?.toFloatOrNull() ?: 0f
|
||||
date_upload = ep.createdAt?.toDate() ?: 0L
|
||||
date_upload = DATE_FORMATTER.tryParse(ep.createdAt)
|
||||
setUrlWithoutDomain(url)
|
||||
}
|
||||
}
|
||||
@@ -198,29 +198,25 @@ class Katanime :
|
||||
}.getOrElse { emptyList<SEpisode>() to 1 }
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
val videoList = mutableListOf<Video>()
|
||||
document.select("[data-player]:not([data-player-name=\"Mega\"])").forEach { element ->
|
||||
runCatching {
|
||||
val serverTitle = element.ownText().trim()
|
||||
val dataPlayer = element.attr("data-player")
|
||||
val playerDocument = client.newCall(GET("$baseUrl/reproductor?url=$dataPlayer"))
|
||||
.execute()
|
||||
.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
return document.select("[data-player]:not([data-player-name=\"Mega\"])").parallelCatchingFlatMapBlocking { element ->
|
||||
val serverTitle = element.ownText().trim()
|
||||
val dataPlayer = element.attr("data-player")
|
||||
val playerDocument = client.newCall(GET("$baseUrl/reproductor?url=$dataPlayer"))
|
||||
.awaitSuccess()
|
||||
.useAsJsoup()
|
||||
|
||||
val encryptedData = playerDocument
|
||||
.selectFirst("script:containsData(var e =)")?.data()
|
||||
?.substringAfter("var e = '")?.substringBefore("';")
|
||||
?: return emptyList()
|
||||
val encryptedData = playerDocument
|
||||
.selectFirst("script:containsData(var e =)")?.data()
|
||||
?.substringAfter("var e = '")?.substringBefore("';")
|
||||
?: return@parallelCatchingFlatMapBlocking emptyList()
|
||||
|
||||
val json = encryptedData.parseAs<CryptoDto>()
|
||||
val decryptedLink = CryptoAES.decryptWithSalt(json.ct!!, json.s!!, DECRYPTION_PASSWORD)
|
||||
.replace("\\/", "/").replace("\"", "")
|
||||
val json = encryptedData.parseAs<CryptoDto>()
|
||||
val decryptedLink = CryptoAES.decryptWithSalt(json.ct!!, json.s!!, DECRYPTION_PASSWORD)
|
||||
.replace("\\/", "/").replace("\"", "")
|
||||
|
||||
serverVideoResolver(decryptedLink, serverTitle).also(videoList::addAll)
|
||||
}
|
||||
serverVideoResolver(decryptedLink, serverTitle)
|
||||
}
|
||||
return videoList
|
||||
}
|
||||
|
||||
private val doodExtractor by lazy { DoodExtractor(client) }
|
||||
@@ -231,7 +227,7 @@ class Katanime :
|
||||
private val mp4uploadExtractor by lazy { Mp4uploadExtractor(client) }
|
||||
private val unpackerExtractor by lazy { UnpackerExtractor(client, headers) }
|
||||
|
||||
private fun serverVideoResolver(url: String, serverOpt: String): List<Video> {
|
||||
private suspend fun serverVideoResolver(url: String, serverOpt: String): List<Video> {
|
||||
val matched = conventions.firstOrNull { (_, names) -> names.any { it.lowercase() in url.lowercase() || it.lowercase() in serverOpt.lowercase() } }?.first
|
||||
return when (matched) {
|
||||
"streamwish" -> StreamWishExtractor(client, headers).videosFromUrl(url, videoNameGen = { "StreamWish:$it" })
|
||||
@@ -305,8 +301,6 @@ class Katanime :
|
||||
maxOf(1, ceil(this).toInt())
|
||||
}
|
||||
|
||||
private fun String.toDate(): Long = runCatching { DATE_FORMATTER.parse(trim())?.time }.getOrNull() ?: 0L
|
||||
|
||||
private fun Element.getImageUrl(): String? = when {
|
||||
isValidUrl("data-src") -> attr("abs:data-src")
|
||||
isValidUrl("data-lazy-src") -> attr("abs:data-lazy-src")
|
||||
|
||||
@@ -18,9 +18,10 @@ 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.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import keiyoushi.utils.getPreferencesLazy
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
@@ -66,14 +67,14 @@ class MetroSeries :
|
||||
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/cartelera-series/page/$page", headers)
|
||||
|
||||
override fun popularAnimeParse(response: Response): AnimesPage {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
val elements = document.select(".post")
|
||||
val nextPage = document.select(".nav-links .current ~ a").any()
|
||||
val animeList = elements.map { element ->
|
||||
SAnime.create().apply {
|
||||
setUrlWithoutDomain(element.selectFirst(".lnk-blk")?.attr("abs:href") ?: "")
|
||||
title = element.selectFirst(".entry-header .entry-title")?.text() ?: ""
|
||||
description = element.select(".entry-content p").text() ?: ""
|
||||
description = element.select(".entry-content p").text()
|
||||
thumbnail_url = element.selectFirst(".post-thumbnail figure img")?.let { getImageUrl(it) }
|
||||
}
|
||||
}
|
||||
@@ -89,7 +90,7 @@ class MetroSeries :
|
||||
override fun searchAnimeParse(response: Response) = popularAnimeParse(response)
|
||||
|
||||
override fun animeDetailsParse(response: Response): SAnime {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
return SAnime.create().apply {
|
||||
title = document.selectFirst("aside .entry-header .entry-title")?.text() ?: ""
|
||||
description = document.select("aside .description p:not([class])").joinToString { it.text() }
|
||||
@@ -106,7 +107,7 @@ class MetroSeries :
|
||||
}
|
||||
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
val referer = response.request.url.toString()
|
||||
return if (referer.contains("pelicula")) {
|
||||
listOf(
|
||||
@@ -130,7 +131,7 @@ class MetroSeries :
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDetailSeason(element: Element, referer: String): List<SEpisode> = try {
|
||||
private suspend fun getDetailSeason(element: Element, referer: String): List<SEpisode> {
|
||||
val post = element.attr("data-post")
|
||||
val season = element.attr("data-season")
|
||||
val formBody = FormBody.Builder()
|
||||
@@ -146,9 +147,9 @@ class MetroSeries :
|
||||
.header("Referer", referer)
|
||||
.header("Content-Type", "application/x-www-form-urlencoded")
|
||||
.build()
|
||||
val detail = client.newCall(request).execute().asJsoup()
|
||||
val detail = client.newCall(request).awaitSuccess().useAsJsoup()
|
||||
|
||||
detail.select(".post").reversed().mapIndexed { idx, it ->
|
||||
return detail.select(".post").reversed().mapIndexed { idx, it ->
|
||||
val epNumber = try {
|
||||
it.select(".entry-header .num-epi").text().substringAfter("x").substringBefore("–").trim()
|
||||
} catch (_: Exception) {
|
||||
@@ -161,62 +162,65 @@ class MetroSeries :
|
||||
episode_number = epNumber.toFloat()
|
||||
}
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
emptyList()
|
||||
}
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
val videoList = mutableListOf<Video>()
|
||||
document.select(".aa-tbs-video a").forEach {
|
||||
val prefix = runCatching {
|
||||
val lang = it.select(".server").text().lowercase()
|
||||
when {
|
||||
lang.contains("latino") -> "[LAT]"
|
||||
lang.contains("castellano") -> "[CAST]"
|
||||
lang.contains("sub") || lang.contains("vose") -> "[SUB]"
|
||||
else -> ""
|
||||
}
|
||||
}.getOrDefault("")
|
||||
val document = response.useAsJsoup()
|
||||
return document.select(".aa-tbs-video a").parallelCatchingFlatMapBlocking {
|
||||
val lang = it.select(".server").text().lowercase()
|
||||
val prefix = when {
|
||||
lang.contains("latino") -> "[LAT]"
|
||||
lang.contains("castellano") -> "[CAST]"
|
||||
lang.contains("sub") || lang.contains("vose") -> "[SUB]"
|
||||
else -> ""
|
||||
}
|
||||
|
||||
val ide = it.attr("href")
|
||||
var src = document.select("$ide iframe").attr("data-src").replace("#038;", "&").replace("&", "")
|
||||
try {
|
||||
if (src.contains("metro")) {
|
||||
src = client.newCall(GET(src)).execute().asJsoup().selectFirst("iframe")?.attr("src") ?: ""
|
||||
}
|
||||
if (src.contains("metro")) {
|
||||
src = client.newCall(GET(src)).awaitSuccess().useAsJsoup().selectFirst("iframe")?.attr("src") ?: ""
|
||||
}
|
||||
|
||||
if (src.contains("fastream")) {
|
||||
when {
|
||||
src.contains("fastream") -> {
|
||||
if (src.contains("emb.html")) {
|
||||
val key = src.split("/").last()
|
||||
src = "https://fastream.to/embed-$key.html"
|
||||
}
|
||||
FastreamExtractor(client, headers).videosFromUrl(src, needsSleep = false, prefix = "$prefix Fastream:").also(videoList::addAll)
|
||||
FastreamExtractor(client, headers).videosFromUrl(src, needsSleep = false, prefix = "$prefix Fastream:")
|
||||
}
|
||||
if (src.contains("upstream")) {
|
||||
UpstreamExtractor(client).videosFromUrl(src, prefix = "$prefix ").let { videoList.addAll(it) }
|
||||
|
||||
src.contains("upstream") -> {
|
||||
UpstreamExtractor(client).videosFromUrl(src, prefix = "$prefix ")
|
||||
}
|
||||
if (src.contains("yourupload")) {
|
||||
YourUploadExtractor(client).videoFromUrl(src, headers, prefix = "$prefix ").let { videoList.addAll(it) }
|
||||
|
||||
src.contains("yourupload") -> {
|
||||
YourUploadExtractor(client).videoFromUrl(src, headers, prefix = "$prefix ")
|
||||
}
|
||||
if (src.contains("voe")) {
|
||||
VoeExtractor(client, headers).videosFromUrl(src, prefix = "$prefix ").also(videoList::addAll)
|
||||
|
||||
src.contains("voe") -> {
|
||||
VoeExtractor(client, headers).videosFromUrl(src, prefix = "$prefix ")
|
||||
}
|
||||
if (src.contains("wishembed") || src.contains("streamwish") || src.contains("wish")) {
|
||||
StreamWishExtractor(client, headers).videosFromUrl(src) { "$prefix StreamWish:$it" }.also(videoList::addAll)
|
||||
|
||||
src.contains("wishembed") || src.contains("streamwish") || src.contains("wish") -> {
|
||||
StreamWishExtractor(client, headers).videosFromUrl(src) { "$prefix StreamWish:$it" }
|
||||
}
|
||||
if (src.contains("mp4upload")) {
|
||||
Mp4uploadExtractor(client).videosFromUrl(src, headers, prefix = "$prefix ").let { videoList.addAll(it) }
|
||||
|
||||
src.contains("mp4upload") -> {
|
||||
Mp4uploadExtractor(client).videosFromUrl(src, headers, prefix = "$prefix ")
|
||||
}
|
||||
if (src.contains("burst")) {
|
||||
BurstCloudExtractor(client).videoFromUrl(src, headers = headers, prefix = "$prefix ").let { videoList.addAll(it) }
|
||||
|
||||
src.contains("burst") -> {
|
||||
BurstCloudExtractor(client).videoFromUrl(src, headers = headers, prefix = "$prefix ")
|
||||
}
|
||||
if (src.contains("filemoon") || src.contains("moonplayer")) {
|
||||
FilemoonExtractor(client).videosFromUrl(src, headers = headers, prefix = "$prefix Filemoon:").let { videoList.addAll(it) }
|
||||
|
||||
src.contains("filemoon") || src.contains("moonplayer") -> {
|
||||
FilemoonExtractor(client).videosFromUrl(src, headers = headers, prefix = "$prefix Filemoon:")
|
||||
}
|
||||
} catch (_: Exception) {}
|
||||
|
||||
else -> emptyList()
|
||||
}
|
||||
}
|
||||
return videoList
|
||||
}
|
||||
|
||||
override fun List<Video>.sort(): List<Video> {
|
||||
@@ -241,13 +245,6 @@ class MetroSeries :
|
||||
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 {
|
||||
@@ -257,13 +254,6 @@ class MetroSeries :
|
||||
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 {
|
||||
@@ -273,13 +263,6 @@ class MetroSeries :
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,9 +18,12 @@ 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.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import keiyoushi.utils.getPreferencesLazy
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.parallelMapNotNullBlocking
|
||||
import keiyoushi.utils.tryParse
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
@@ -131,8 +134,7 @@ class OtakuFR :
|
||||
?: 1F
|
||||
date_upload = element.selectFirst("span")
|
||||
?.text()
|
||||
?.let(::parseDate)
|
||||
?: 0L
|
||||
.let(DATE_FORMATTER::tryParse)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,20 +150,23 @@ class OtakuFR :
|
||||
private val sibnetExtractor by lazy { SibnetExtractor(client) }
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
|
||||
val serversList = document.select("div.tab-content iframe[src]").mapNotNull {
|
||||
val serversList = document.select("div.tab-content iframe[src]").parallelMapNotNullBlocking {
|
||||
val url = it.attr("abs:data-src")
|
||||
|
||||
if (url.contains("parisanime.com")) {
|
||||
val docHeaders = headers.newBuilder().apply {
|
||||
set("X-Requested-With", "XMLHttpRequest")
|
||||
}.build()
|
||||
val newDoc = client.newCall(
|
||||
GET(url, headers = docHeaders),
|
||||
).execute().asJsoup()
|
||||
val newDoc = runCatching {
|
||||
client.newCall(
|
||||
GET(url, headers = docHeaders),
|
||||
).awaitSuccess().useAsJsoup()
|
||||
}.getOrNull() ?: return@parallelMapNotNullBlocking null
|
||||
val resUrl = newDoc.selectFirst("div[data-url]")?.attr("data-url")
|
||||
if (resUrl!!.startsWith("//")) "https:$resUrl" else resUrl
|
||||
?: return@parallelMapNotNullBlocking null
|
||||
if (resUrl.startsWith("//")) "https:$resUrl" else resUrl
|
||||
} else {
|
||||
url
|
||||
}
|
||||
@@ -207,9 +212,6 @@ class OtakuFR :
|
||||
}.build().toString()
|
||||
}
|
||||
|
||||
private fun parseDate(dateStr: String): Long = runCatching { DATE_FORMATTER.parse(dateStr)?.time }
|
||||
.getOrNull() ?: 0L
|
||||
|
||||
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)!!
|
||||
@@ -262,13 +264,6 @@ class OtakuFR :
|
||||
entryValues = arrayOf("1080", "720", "480", "360")
|
||||
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 {
|
||||
@@ -278,13 +273,6 @@ class OtakuFR :
|
||||
entryValues = HOSTERS
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
@@ -13,11 +13,15 @@ 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.network.GET
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import keiyoushi.utils.bodyString
|
||||
import keiyoushi.utils.getPreferencesLazy
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import okhttp3.Response
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
|
||||
@@ -100,12 +104,12 @@ class Kuramanime :
|
||||
|
||||
// ============================== Episodes ==============================
|
||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
|
||||
val html = document.selectFirst(episodeListSelector())?.attr("data-content")
|
||||
?: return emptyList()
|
||||
|
||||
val newDoc = response.asJsoup(html)
|
||||
val newDoc = Jsoup.parse(html)
|
||||
|
||||
val limits = newDoc.select("a.btn-secondary")
|
||||
|
||||
@@ -155,7 +159,7 @@ class Kuramanime :
|
||||
private val vidguardExtractor by lazy { VidGuardExtractor(client) }
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val doc = response.asJsoup()
|
||||
val doc = response.useAsJsoup()
|
||||
|
||||
val scriptData = doc.selectFirst("[data-kps]")?.attr("data-kps")
|
||||
?.let(::getScriptData)
|
||||
@@ -176,48 +180,45 @@ class Kuramanime :
|
||||
.set("X-Requested-With", "XMLHttpRequest")
|
||||
.build()
|
||||
|
||||
return servers.flatMap { (server, serverName) ->
|
||||
runCatching {
|
||||
val newHeaders = headers.newBuilder()
|
||||
.set("X-CSRF-TOKEN", csrfToken)
|
||||
.set("X-Fuck-ID", scriptData.tokenId)
|
||||
.set("X-Request-ID", getRandomString())
|
||||
.set("X-Request-Index", "0")
|
||||
.build()
|
||||
return servers.parallelCatchingFlatMapBlocking { (server, serverName) ->
|
||||
val newHeaders = headers.newBuilder()
|
||||
.set("X-CSRF-TOKEN", csrfToken)
|
||||
.set("X-Fuck-ID", scriptData.tokenId)
|
||||
.set("X-Request-ID", getRandomString())
|
||||
.set("X-Request-Index", "0")
|
||||
.build()
|
||||
|
||||
val hash = client.newCall(GET("$baseUrl/" + scriptData.authPath, newHeaders)).execute()
|
||||
.body.string()
|
||||
.trim('"')
|
||||
val hash = client.newCall(GET("$baseUrl/" + scriptData.authPath, newHeaders))
|
||||
.awaitSuccess()
|
||||
.bodyString()
|
||||
.trim('"')
|
||||
|
||||
val newUrl = episodeUrl.newBuilder()
|
||||
.addQueryParameter(scriptData.tokenParam, hash)
|
||||
.addQueryParameter(scriptData.serverParam, server)
|
||||
.build()
|
||||
val newUrl = episodeUrl.newBuilder()
|
||||
.addQueryParameter(scriptData.tokenParam, hash)
|
||||
.addQueryParameter(scriptData.serverParam, server)
|
||||
.build()
|
||||
|
||||
val playerDoc = client.newCall(GET(newUrl.toString(), headers)).execute()
|
||||
.asJsoup()
|
||||
val playerDoc = client.newCall(GET(newUrl.toString(), headers))
|
||||
.awaitSuccess()
|
||||
.useAsJsoup()
|
||||
|
||||
val url = playerDoc.selectFirst("div.video-content iframe")?.attr("src")
|
||||
when {
|
||||
// server == "filelions" && url != null -> streamtapeExtractor.videosFromUrl(url)
|
||||
server == "filemoon" && url != null -> filemoonExtractor.videosFromUrl(url)
|
||||
val url = playerDoc.selectFirst("div.video-content iframe")?.attr("src")
|
||||
when (server) {
|
||||
"filelions" if url != null -> streamWishExtractor.videosFromUrl(url)
|
||||
"filemoon" if url != null -> filemoonExtractor.videosFromUrl(url)
|
||||
|
||||
// mega.nz source
|
||||
// server == "mega" && url != null -> streamtapeExtractor.videosFromUrl(url)
|
||||
server == "streamwish" && url != null -> streamWishExtractor.videosFromUrl(url)
|
||||
|
||||
server == "streamtape" && url != null -> streamtapeExtractor.videosFromUrl(url)
|
||||
|
||||
server == "vidguard" && url != null -> vidguardExtractor.videosFromUrl(url)
|
||||
|
||||
else -> {
|
||||
playerDoc.select("video#player > source").map {
|
||||
val src = it.attr("src")
|
||||
Video(src, "${it.attr("size")}p - $serverName", src)
|
||||
}
|
||||
// mega.nz source
|
||||
// server == "mega" && url != null -> streamtapeExtractor.videosFromUrl(url)
|
||||
"streamwish" if url != null -> streamWishExtractor.videosFromUrl(url)
|
||||
"streamtape" if url != null -> streamtapeExtractor.videosFromUrl(url)
|
||||
"vidguard" if url != null -> vidguardExtractor.videosFromUrl(url)
|
||||
else -> {
|
||||
playerDoc.select("video#player > source").map {
|
||||
val src = it.attr("src")
|
||||
Video(src, "${it.attr("size")}p - $serverName", src)
|
||||
}
|
||||
}
|
||||
}.getOrElse { emptyList() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,7 +227,7 @@ class Kuramanime :
|
||||
|
||||
return runCatching {
|
||||
val response = client.newCall(GET(assetsUrl, headers)).execute()
|
||||
.body.string()
|
||||
.bodyString()
|
||||
|
||||
// Extract the data from the window.process assignment
|
||||
val processEnvRegex = Regex("""window\.process\s*=\s*\{[\s\S]*?env:\s*\{([\s\S]*?)\}[\s\S]*?\}""")
|
||||
|
||||
@@ -16,11 +16,13 @@ import eu.kanade.tachiyomi.animesource.model.Video
|
||||
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import keiyoushi.utils.bodyString
|
||||
import keiyoushi.utils.getPreferencesLazy
|
||||
import keiyoushi.utils.parallelCatchingFlatMapBlocking
|
||||
import keiyoushi.utils.parallelMapNotNullBlocking
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import keiyoushi.utils.tryParse
|
||||
import keiyoushi.utils.useAsJsoup
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
@@ -78,7 +80,7 @@ class OtakuDesu :
|
||||
.toFloatOrNull() ?: 1F
|
||||
setUrlWithoutDomain(link.attr("href"))
|
||||
name = text.replace(nameRegex, "")
|
||||
date_upload = element.selectFirst("span.zeebr")?.text().toDate()
|
||||
date_upload = element.selectFirst("span.zeebr")?.text().let(DATE_FORMATTER::tryParse)
|
||||
}
|
||||
|
||||
override fun episodeListSelector() = "#venkonten > div.venser > div:nth-child(8) > ul > li"
|
||||
@@ -140,7 +142,7 @@ class OtakuDesu :
|
||||
private val genreSelector = ".col-anime"
|
||||
|
||||
override fun searchAnimeParse(response: Response): AnimesPage {
|
||||
val document = response.asJsoup()
|
||||
val document = response.useAsJsoup()
|
||||
|
||||
val ui = when {
|
||||
document.selectFirst(genreSelector) == null -> "search"
|
||||
@@ -163,7 +165,7 @@ class OtakuDesu :
|
||||
override fun videoListSelector() = "div.mirrorstream ul li > a"
|
||||
|
||||
override fun videoListParse(response: Response): List<Video> {
|
||||
val doc = response.asJsoup()
|
||||
val doc = response.useAsJsoup()
|
||||
val script = doc.selectFirst("script:containsData({action:)")!!
|
||||
.data()
|
||||
|
||||
@@ -181,7 +183,7 @@ class OtakuDesu :
|
||||
}
|
||||
}
|
||||
|
||||
private fun getEmbedLinks(element: Element, action: String, nonce: String): Pair<String, String> {
|
||||
private suspend fun getEmbedLinks(element: Element, action: String, nonce: String): Pair<String, String> {
|
||||
val decodedData = element.attr("data-content").b64Decode()
|
||||
.drop(1)
|
||||
.dropLast(1)
|
||||
@@ -199,8 +201,8 @@ class OtakuDesu :
|
||||
}.build()
|
||||
|
||||
val doc = client.newCall(POST("$baseUrl/wp-admin/admin-ajax.php", body = form))
|
||||
.execute()
|
||||
.body.string()
|
||||
.awaitSuccess()
|
||||
.bodyString()
|
||||
.substringAfter(":\"")
|
||||
.substringBefore('"')
|
||||
.b64Decode()
|
||||
@@ -215,7 +217,7 @@ class OtakuDesu :
|
||||
private val yourUploadExtractor by lazy { YourUploadExtractor(client) }
|
||||
private val vidHideExtractor by lazy { VidHideExtractor(client, headers) }
|
||||
|
||||
private fun getVideosFromEmbed(quality: String, link: String): List<Video> = when {
|
||||
private suspend fun getVideosFromEmbed(quality: String, link: String): List<Video> = when {
|
||||
"filelions" in link -> {
|
||||
filelionsExtractor.videosFromUrl(link, videoNameGen = { "FileLions - $it" })
|
||||
}
|
||||
@@ -227,8 +229,8 @@ class OtakuDesu :
|
||||
}
|
||||
|
||||
"desustream" in link -> {
|
||||
client.newCall(GET(link, headers)).execute().let {
|
||||
val doc = it.asJsoup()
|
||||
client.newCall(GET(link, headers)).awaitSuccess().let {
|
||||
val doc = it.useAsJsoup()
|
||||
val script = doc.selectFirst("script:containsData(sources)")!!.data()
|
||||
val videoUrl = script.substringAfter("sources:[{")
|
||||
.substringAfter("file':'")
|
||||
@@ -238,8 +240,8 @@ class OtakuDesu :
|
||||
}
|
||||
|
||||
"mp4upload" in link -> {
|
||||
client.newCall(GET(link, headers)).execute().let {
|
||||
val doc = it.asJsoup()
|
||||
client.newCall(GET(link, headers)).awaitSuccess().let {
|
||||
val doc = it.useAsJsoup()
|
||||
val script = doc.selectFirst("script:containsData(player.src)")!!.data()
|
||||
val videoUrl = script.substringAfter("src: \"").substringBefore('"')
|
||||
listOf(Video(videoUrl, "Mp4upload - $quality", videoUrl, headers))
|
||||
@@ -247,7 +249,7 @@ class OtakuDesu :
|
||||
}
|
||||
|
||||
"vidhide" in link -> {
|
||||
runBlocking { vidHideExtractor.videosFromUrl(link) }
|
||||
vidHideExtractor.videosFromUrl(link)
|
||||
}
|
||||
|
||||
else -> emptyList()
|
||||
@@ -257,7 +259,7 @@ class OtakuDesu :
|
||||
val form = FormBody.Builder().add("action", action).build()
|
||||
return client.newCall(POST("$baseUrl/wp-admin/admin-ajax.php", body = form))
|
||||
.execute()
|
||||
.body.string()
|
||||
.bodyString()
|
||||
.substringAfter(":\"")
|
||||
.substringBefore('"')
|
||||
}
|
||||
@@ -328,12 +330,6 @@ class OtakuDesu :
|
||||
entryValues = PREF_QUALITY_ENTRIES
|
||||
setDefaultValue(PREF_QUALITY_DEFAULT)
|
||||
summary = "%s"
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
val selected = newValue as String
|
||||
val index = findIndexOfValue(selected)
|
||||
val entry = entryValues[index] as String
|
||||
preferences.edit().putString(key, entry).commit()
|
||||
}
|
||||
}
|
||||
screen.addPreference(videoQualityPref)
|
||||
}
|
||||
@@ -347,9 +343,6 @@ class OtakuDesu :
|
||||
}.trim()
|
||||
}
|
||||
|
||||
private fun String?.toDate(): Long = runCatching { DATE_FORMATTER.parse(this?.trim() ?: "")?.time }
|
||||
.getOrNull() ?: 0L
|
||||
|
||||
override fun List<Video>.sort(): List<Video> {
|
||||
val quality = preferences.getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
|
||||
return sortedWith(
|
||||
|
||||
Reference in New Issue
Block a user