diff --git a/src/es/pandrama/build.gradle b/src/es/pandrama/build.gradle new file mode 100644 index 0000000000..8d2ffc3a46 --- /dev/null +++ b/src/es/pandrama/build.gradle @@ -0,0 +1,12 @@ +ext { + extName = 'Pandrama' + extClass = '.Pandrama' + extVersionCode = 1 +} + +apply from: "$rootDir/common.gradle" + +dependencies { + implementation(project(':lib:okru-extractor')) + implementation(project(':lib:vk-extractor')) +} \ No newline at end of file diff --git a/src/es/pandrama/res/mipmap-hdpi/ic_launcher.png b/src/es/pandrama/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..1ef342f62a Binary files /dev/null and b/src/es/pandrama/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/es/pandrama/res/mipmap-mdpi/ic_launcher.png b/src/es/pandrama/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..f3e861e68c Binary files /dev/null and b/src/es/pandrama/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/es/pandrama/res/mipmap-xhdpi/ic_launcher.png b/src/es/pandrama/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..b43507def3 Binary files /dev/null and b/src/es/pandrama/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/es/pandrama/res/mipmap-xxhdpi/ic_launcher.png b/src/es/pandrama/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..5986e42bae Binary files /dev/null and b/src/es/pandrama/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/es/pandrama/res/mipmap-xxxhdpi/ic_launcher.png b/src/es/pandrama/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..360e081459 Binary files /dev/null and b/src/es/pandrama/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/es/pandrama/src/eu/kanade/tachiyomi/animeextension/es/pandrama/Pandrama.kt b/src/es/pandrama/src/eu/kanade/tachiyomi/animeextension/es/pandrama/Pandrama.kt new file mode 100644 index 0000000000..0f4649da27 --- /dev/null +++ b/src/es/pandrama/src/eu/kanade/tachiyomi/animeextension/es/pandrama/Pandrama.kt @@ -0,0 +1,299 @@ +package eu.kanade.tachiyomi.animeextension.es.pandrama + +import android.app.Application +import android.content.SharedPreferences +import androidx.preference.ListPreference +import androidx.preference.PreferenceScreen +import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource +import eu.kanade.tachiyomi.animesource.model.AnimeFilterList +import eu.kanade.tachiyomi.animesource.model.AnimesPage +import eu.kanade.tachiyomi.animesource.model.SAnime +import eu.kanade.tachiyomi.animesource.model.SEpisode +import eu.kanade.tachiyomi.animesource.model.Video +import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource +import eu.kanade.tachiyomi.lib.okruextractor.OkruExtractor +import eu.kanade.tachiyomi.lib.vkextractor.VkExtractor +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.util.asJsoup +import eu.kanade.tachiyomi.util.parallelCatchingFlatMapBlocking +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import okhttp3.Request +import okhttp3.Response +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import uy.kohesive.injekt.injectLazy +import java.net.URLDecoder + +class Pandrama : ConfigurableAnimeSource, AnimeHttpSource() { + + override val name = "Pandrama" + + override val baseUrl = "https://pandra.ma" + + override val lang = "es" + + override val supportsLatest = true + + private val preferences: SharedPreferences by lazy { + Injekt.get().getSharedPreferences("source_$id", 0x0000) + } + + private val json: Json by injectLazy() + + companion object { + private const val PREF_QUALITY_KEY = "preferred_quality" + private const val PREF_QUALITY_DEFAULT = "1080" + private val QUALITY_LIST = arrayOf("1080", "720", "480", "360") + + private const val PREF_SERVER_KEY = "preferred_server" + private const val PREF_SERVER_DEFAULT = "Vk" + private val SERVER_LIST = arrayOf("Vk", "Okru") + } + + override fun animeDetailsParse(response: Response): SAnime { + val document = response.asJsoup() + val details = SAnime.create() + + for (element in document.select(".hl-full-box ul li")) { + val title = element.select("em").text() + val content = element.select("span") + + when { + title.contains("Director:") -> details.author = element.ownText().trim() + title.contains("Estado:") -> details.status = parseStatus(content.text()) + title.contains("Protagonistas:") -> details.artist = element.selectFirst("a")?.text() + title.contains("Género:") -> details.genre = element.select("a").joinToString { it.text() } + title.contains("Sinopsis:") -> details.description = element.ownText().trim() + } + } + + return details + } + + private fun parseStatus(status: String?) = when (status.orEmpty()) { + "En Emisión" -> SAnime.ONGOING + "Finalizado" -> SAnime.COMPLETED + else -> SAnime.UNKNOWN + } + + override fun popularAnimeRequest(page: Int) = GET("$baseUrl/vodshow/Dramas--------$page---/", headers) + + override fun popularAnimeParse(response: Response): AnimesPage { + val document = response.asJsoup() + val elements = document.select(".hl-vod-list li > a") + val nextPage = document.select(".hl-page-wrap a:not(.hl-disad) span:contains(siguiente)").any() + val animeList = elements.map { element -> + val langTag = element.select(".hl-pic-tag").text().trim() + val prefix = when { + langTag.contains("Español LAT") -> "\uD83C\uDDF2\uD83C\uDDFD " + langTag.contains("Español ES") -> "\uD83C\uDDEA\uD83C\uDDF8 " + else -> "" + } + SAnime.create().apply { + title = """$prefix ${element.attr("title")}""".trim() + thumbnail_url = element.attr("abs:data-original") + setUrlWithoutDomain(element.attr("abs:href")) + } + } + return AnimesPage(animeList, nextPage) + } + + override fun latestUpdatesParse(response: Response) = popularAnimeParse(response) + + override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/vodshow/Dramas--hits------$page---/", headers) + + override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { + val url = when { + query.isNotBlank() -> "$baseUrl/vodsearch/$query----------$page---/" + else -> "$baseUrl/vodshow/Dramas--------$page---/" + } + return GET(url, headers) + } + + override fun searchAnimeParse(response: Response): AnimesPage { + val document = response.asJsoup() + if (!document.location().contains("vodsearch")) return popularAnimeParse(response) + val elements = document.select(".hl-one-list .hl-item-thumb") + val nextPage = document.select(".hl-page-wrap a:not(.hl-disad) span:contains(siguiente)").any() + val animeList = elements.map { element -> + SAnime.create().apply { + title = element.attr("title") + thumbnail_url = element.attr("abs:data-original") + setUrlWithoutDomain(element.attr("abs:href")) + } + } + return AnimesPage(animeList, nextPage) + } + + override fun episodeListParse(response: Response): List { + val document = response.asJsoup() + return document.select(".hl-plays-list li > a").groupBy { it.text().trim() }.map { + val urlList = json.encodeToString(it.value.map { it.attr("abs:href") }) + SEpisode.create().apply { + name = it.key + episode_number = it.key.substringAfter("Ep.").trim().toFloatOrNull() ?: 0F + url = urlList + } + }.reversed() + } + + override suspend fun getVideoList(episode: SEpisode): List