From 5a7c722b17898beb8b7241058da7067e3347e317 Mon Sep 17 00:00:00 2001 From: Claudemirovsky <63046606+Claudemirovsky@users.noreply.github.com> Date: Sun, 19 Nov 2023 06:15:48 -0300 Subject: [PATCH] fix(id/kuramanime): Fix video extractor + refactor (#2525) --- src/id/kuramanime/build.gradle | 12 +- .../id/kuramanime/Kuramanime.kt | 264 ++++++++++-------- 2 files changed, 159 insertions(+), 117 deletions(-) diff --git a/src/id/kuramanime/build.gradle b/src/id/kuramanime/build.gradle index cfa5ea49c2..e52e4a29a6 100644 --- a/src/id/kuramanime/build.gradle +++ b/src/id/kuramanime/build.gradle @@ -1,12 +1,18 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) +} ext { extName = 'Kuramanime' pkgNameSuffix = 'id.kuramanime' extClass = '.Kuramanime' - extVersionCode = 8 + extVersionCode = 9 libVersion = '13' } +dependencies { + implementation(project(":lib-streamtape-extractor")) +} + apply from: "$rootDir/common.gradle" diff --git a/src/id/kuramanime/src/eu/kanade/tachiyomi/animeextension/id/kuramanime/Kuramanime.kt b/src/id/kuramanime/src/eu/kanade/tachiyomi/animeextension/id/kuramanime/Kuramanime.kt index 77b3c06ce5..32f685d4f2 100644 --- a/src/id/kuramanime/src/eu/kanade/tachiyomi/animeextension/id/kuramanime/Kuramanime.kt +++ b/src/id/kuramanime/src/eu/kanade/tachiyomi/animeextension/id/kuramanime/Kuramanime.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.animeextension.id.kuramanime import android.app.Application -import android.content.SharedPreferences +import android.util.Base64 import androidx.preference.ListPreference import androidx.preference.PreferenceScreen import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource @@ -10,17 +10,17 @@ import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.animesource.model.SEpisode import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource +import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.OkHttpClient -import okhttp3.Request +import okhttp3.Headers import okhttp3.Response -import org.jsoup.Jsoup import org.jsoup.nodes.Document import org.jsoup.nodes.Element import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.lang.Exception +import java.net.URLEncoder class Kuramanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() { override val name = "Kuramanime" @@ -31,160 +31,189 @@ class Kuramanime : ConfigurableAnimeSource, ParsedAnimeHttpSource() { override val supportsLatest = true - override val client: OkHttpClient = network.cloudflareClient + override val client = network.cloudflareClient - private val preferences: SharedPreferences by lazy { + private val preferences by lazy { Injekt.get().getSharedPreferences("source_$id", 0x0000) } - override fun animeDetailsParse(document: Document): SAnime { - val anime = SAnime.create() - val status = parseStatus(document.select("div.anime__details__widget > div > div:nth-child(1) > ul > li:nth-child(3)").text().replace("Status: ", "")) - anime.title = document.select("div.anime__details__title > h3").text().replace("Judul: ", "") - anime.genre = document.select("div.anime__details__widget > div > div:nth-child(2) > ul > li:nth-child(1)").text().replace("Genre: ", "") - anime.status = status - anime.artist = document.select("div.anime__details__widget > div > div:nth-child(2) > ul > li:nth-child(5)").text() - anime.author = "UNKNOWN" - return anime - } + // ============================== Popular =============================== + override fun popularAnimeRequest(page: Int) = GET("$baseUrl/anime?page=$page") - private fun parseStatus(statusString: String): Int { - return when (statusString) { - "Sedang Tayang" -> SAnime.ONGOING - "Selesai Tayang" -> SAnime.COMPLETED - else -> SAnime.UNKNOWN - } + override fun popularAnimeSelector() = "div.product__item" + + override fun popularAnimeFromElement(element: Element) = SAnime.create().apply { + setUrlWithoutDomain(element.selectFirst("a")!!.attr("href")) + thumbnail_url = element.selectFirst("a > div")?.attr("data-setbg") + title = element.selectFirst("div.product__item__text > h5")!!.text() } - override fun episodeFromElement(element: Element): SEpisode { - val episode = SEpisode.create() - val epsNum = getNumberFromEpsString(element.text()) - episode.setUrlWithoutDomain(element.attr("href")) - episode.episode_number = when { - epsNum.isNotEmpty() -> epsNum.toFloatOrNull() ?: 1F - else -> 1F - } - episode.name = element.text() + override fun popularAnimeNextPageSelector() = "div.product__pagination > a:last-child" - return episode - } + // =============================== Latest =============================== + override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/anime?order_by=updated&page=$page") - private fun getNumberFromEpsString(epsStr: String): String { - return epsStr.filter { it.isDigit() } - } + override fun latestUpdatesSelector() = popularAnimeSelector() - override fun episodeListSelector(): String = "#episodeLists" + override fun latestUpdatesFromElement(element: Element) = popularAnimeFromElement(element) - override fun episodeListParse(response: Response): List { - val document = response.asJsoup() + override fun latestUpdatesNextPageSelector() = popularAnimeNextPageSelector() - val html = document.select(episodeListSelector()).attr("data-content") - val jsoupE = Jsoup.parse(html) + // =============================== Search =============================== + override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList) = GET("$baseUrl/anime?search=$query&page=$page") - return jsoupE.select("a").filter { ele -> !ele.attr("href").contains("batch") }.map { episodeFromElement(it) }.reversed() - } + override fun searchAnimeSelector() = popularAnimeSelector() - private fun parseShortInfo(element: Element): SAnime { - val anime = SAnime.create() - anime.setUrlWithoutDomain(element.selectFirst("a")!!.attr("href")) - anime.thumbnail_url = element.selectFirst("a > div")!!.attr("data-setbg") - anime.title = element.select("div.product__item__text > h5").text() - return anime - } + override fun searchAnimeFromElement(element: Element) = popularAnimeFromElement(element) - override fun latestUpdatesFromElement(element: Element): SAnime = parseShortInfo(element) + override fun searchAnimeNextPageSelector() = popularAnimeNextPageSelector() - override fun latestUpdatesNextPageSelector(): String = "div.product__pagination > a:last-child" + // =========================== Anime Details ============================ + override fun animeDetailsParse(document: Document) = SAnime.create().apply { + thumbnail_url = document.selectFirst("div.anime__details__pic")?.attr("data-setbg") - override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/anime?order_by=updated&page=$page") + val details = document.selectFirst("div.anime__details__text")!! - override fun latestUpdatesSelector(): String = "div.product__item" + title = details.selectFirst("div > h3")!!.text().replace("Judul: ", "") - override fun popularAnimeFromElement(element: Element): SAnime = parseShortInfo(element) + val infos = details.selectFirst("div.anime__details__widget")!! + artist = infos.select("li:contains(Studio:) > a").eachText().joinToString().takeUnless(String::isEmpty) + status = parseStatus(infos.selectFirst("li:contains(Status:) > a")?.text()) - override fun popularAnimeNextPageSelector(): String = "div.product__pagination > a:last-child" + genre = infos.select("li:contains(Genre:) > a, li:contains(Tema:) > a, li:contains(Demografis:) > a") + .eachText() + .joinToString { it.trimEnd(',', ' ') } + .takeUnless(String::isEmpty) - override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/anime") + description = buildString { + details.selectFirst("p#synopsisField")?.text()?.also(::append) - override fun popularAnimeSelector(): String = "div.product__item" + details.selectFirst("div.anime__details__title > span")?.text() + ?.also { append("\n\nAlternative names: $it\n") } - override fun searchAnimeFromElement(element: Element): SAnime = parseShortInfo(element) + infos.select("ul > li").eachText().forEach { append("\n$it") } + } + } - override fun searchAnimeNextPageSelector(): String = "div.product__pagination > a:last-child" + private fun parseStatus(statusString: String?): Int { + return when (statusString) { + "Sedang Tayang" -> SAnime.ONGOING + "Selesai Tayang" -> SAnime.COMPLETED + else -> SAnime.UNKNOWN + } + } + + // ============================== Episodes ============================== + override fun episodeListParse(response: Response): List { + val document = response.use { it.asJsoup() } - override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request = GET("$baseUrl/anime?search=$query&page=$page") + val html = document.selectFirst(episodeListSelector())?.attr("data-content") + ?: return emptyList() + + val newDoc = response.asJsoup(html) + + return newDoc.select("a") + .filterNot { it.attr("href").contains("batch") } + .map(::episodeFromElement) + .reversed() + } - override fun searchAnimeSelector(): String = "div.product__item" + override fun episodeListSelector() = "a#episodeLists" + override fun episodeFromElement(element: Element) = SEpisode.create().apply { + setUrlWithoutDomain(element.attr("href")) + name = element.text() + episode_number = name.filter(Char::isDigit).toFloatOrNull() ?: 1F + } + + // ============================ Video Links ============================= override fun videoListSelector() = "video#player > source" + // Shall we add "archive", "archive-v2"? archive.org usually returns a beautiful 403 xD + private val supportedHosters = listOf("kuramadrive", "kuramadrive-v2", "streamtape") + + private val streamtapeExtractor by lazy { StreamTapeExtractor(client) } + override fun videoListParse(response: Response): List