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:
Cuong-Tran
2026-06-03 11:38:55 +07:00
parent 7a4360e344
commit 962964d968
18 changed files with 355 additions and 497 deletions

View File

@@ -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",
)
}
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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"))
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)!!

View File

@@ -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)
}

View File

@@ -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)
}
}

View File

@@ -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)!!

View File

@@ -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("&amp;", "")
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)
}
}

View File

@@ -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")

View File

@@ -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("&amp;", "")
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)
}
}

View File

@@ -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)
}

View File

@@ -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]*?\}""")

View File

@@ -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(