diff --git a/multisrc/overrides/dopeflix/default/additional.gradle b/multisrc/overrides/dopeflix/default/additional.gradle index 5220e8ca3..6e4bbd103 100644 --- a/multisrc/overrides/dopeflix/default/additional.gradle +++ b/multisrc/overrides/dopeflix/default/additional.gradle @@ -1,3 +1,4 @@ dependencies { implementation(project(":lib-dood-extractor")) + implementation(project(":lib-cryptoaes")) } diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/dopeflix/DopeFlix.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/dopeflix/DopeFlix.kt index 004af5826..d87f7085c 100644 --- a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/dopeflix/DopeFlix.kt +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/dopeflix/DopeFlix.kt @@ -6,7 +6,6 @@ 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.Track @@ -16,22 +15,18 @@ import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor import eu.kanade.tachiyomi.multisrc.dopeflix.dto.VideoDto import eu.kanade.tachiyomi.multisrc.dopeflix.extractors.DopeFlixExtractor import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.util.asJsoup import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.runBlocking -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.json.Json import okhttp3.Headers -import okhttp3.HttpUrl.Companion.toHttpUrlOrNull +import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response import org.jsoup.nodes.Document import org.jsoup.nodes.Element -import rx.Observable import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -54,15 +49,9 @@ abstract class DopeFlix( Injekt.get().getSharedPreferences("source_$id", 0x0000) } - private val json = Json { - ignoreUnknownKeys = true - } - - override fun headersBuilder(): Headers.Builder = Headers.Builder() - .add("Referer", "$baseUrl/") + override fun headersBuilder() = super.headersBuilder().add("Referer", "$baseUrl/") // ============================== Popular =============================== - override fun popularAnimeSelector(): String = "div.film_list-wrap div.flw-item div.film-poster" override fun popularAnimeRequest(page: Int): Request { @@ -70,48 +59,103 @@ abstract class DopeFlix( return GET("$baseUrl/$type?page=$page") } - override fun popularAnimeFromElement(element: Element): SAnime { - val anime = SAnime.create() - anime.setUrlWithoutDomain(element.selectFirst("a")!!.attr("href")) - anime.thumbnail_url = element.selectFirst("img")!!.attr("data-src") - anime.title = element.selectFirst("a")!!.attr("title") - return anime + override fun popularAnimeFromElement(element: Element) = SAnime.create().apply { + val ahref = element.selectFirst("a")!! + setUrlWithoutDomain(ahref.attr("href")) + title = ahref.attr("title") + thumbnail_url = element.selectFirst("img")!!.attr("data-src") } - override fun popularAnimeNextPageSelector(): String = "ul.pagination li.page-item a[title=next]" + override fun popularAnimeNextPageSelector() = "ul.pagination li.page-item a[title=next]" + + // =============================== Latest =============================== + override fun latestUpdatesNextPageSelector(): String? = null + + override fun latestUpdatesFromElement(element: Element) = popularAnimeFromElement(element) + + override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/home/") + + override fun latestUpdatesSelector(): String { + val sectionLabel = preferences.getString(PREF_LATEST_KEY, PREF_LATEST_DEFAULT)!! + return "section.block_area:has(h2.cat-heading:contains($sectionLabel)) div.film-poster" + } + + // =============================== Search =============================== + override fun searchAnimeFromElement(element: Element) = popularAnimeFromElement(element) + + override fun searchAnimeNextPageSelector() = popularAnimeNextPageSelector() + + override fun searchAnimeSelector() = popularAnimeSelector() + + override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { + val params = DopeFlixFilters.getSearchParameters(filters) + + val url = if (query.isNotBlank()) { + val fixedQuery = query.replace(" ", "-") + "$baseUrl/search/$fixedQuery?page=$page" + } else { + "$baseUrl/filter?".toHttpUrl().newBuilder() + .addQueryParameter("page", page.toString()) + .addQueryParameter("type", params.type) + .addQueryParameter("quality", params.quality) + .addQueryParameter("release_year", params.releaseYear) + .addQueryParameter("genre", params.genres) + .addQueryParameter("country", params.countries) + .build() + .toString() + } + + return GET(url, headers) + } + + override fun getFilterList() = DopeFlixFilters.FILTER_LIST + + // =========================== Anime Details ============================ + override fun animeDetailsParse(document: Document) = SAnime.create().apply { + thumbnail_url = document.selectFirst("img.film-poster-img")!!.attr("src") + title = document.selectFirst("img.film-poster-img")!!.attr("title") + genre = document.select("div.row-line:contains(Genre) a").eachText().joinToString() + description = document.selectFirst("div.detail_page-watch div.description")!! + .text().replace("Overview:", "") + author = document.select("div.row-line:contains(Production) a").eachText().joinToString() + status = parseStatus(document.selectFirst("li.status span.value")?.text()) + } + + private fun parseStatus(statusString: String?): Int { + return when (statusString?.trim()) { + "Ongoing" -> SAnime.ONGOING + else -> SAnime.COMPLETED + } + } // ============================== Episodes ============================== - override fun episodeListSelector() = throw Exception("not used") override fun episodeListParse(response: Response): List { - val document = response.asJsoup() - val episodeList = mutableListOf() - val infoElement = document.select("div.detail_page-watch") + val document = response.use { it.asJsoup() } + val infoElement = document.selectFirst("div.detail_page-watch")!! val id = infoElement.attr("data-id") val dataType = infoElement.attr("data-type") // Tv = 2 or movie = 1 - if (dataType == "2") { + return if (dataType == "2") { val seasonUrl = "$baseUrl/ajax/v2/tv/seasons/$id" val seasonsHtml = client.newCall( GET( seasonUrl, headers = Headers.headersOf("Referer", document.location()), ), - ).execute().asJsoup() - val seasonsElements = seasonsHtml.select("a.dropdown-item.ss-item") - seasonsElements.forEach { - val seasonEpList = parseEpisodesFromSeries(it) - episodeList.addAll(seasonEpList) - } + ).execute().use { it.asJsoup() } + seasonsHtml + .select("a.dropdown-item.ss-item") + .flatMap(::parseEpisodesFromSeries) + .reversed() } else { val movieUrl = "$baseUrl/ajax/movie/episodes/$id" - val episode = SEpisode.create() - episode.name = document.select("h2.heading-name").text() - episode.episode_number = 1F - episode.setUrlWithoutDomain(movieUrl) - episodeList.add(episode) + SEpisode.create().apply { + name = document.selectFirst("h2.heading-name")!!.text() + episode_number = 1F + setUrlWithoutDomain(movieUrl) + }.let(::listOf) } - return episodeList.reversed() } override fun episodeFromElement(element: Element): SEpisode = throw Exception("not used") @@ -120,99 +164,77 @@ abstract class DopeFlix( val seasonId = element.attr("data-id") val seasonName = element.text() val episodesUrl = "$baseUrl/ajax/v2/season/episodes/$seasonId" - val episodesHtml = client.newCall(GET(episodesUrl)) - .execute() - .asJsoup() + val episodesHtml = client.newCall(GET(episodesUrl)).execute() + .use { it.asJsoup() } val episodeElements = episodesHtml.select("div.eps-item") return episodeElements.map { episodeFromElement(it, seasonName) } } - private fun episodeFromElement(element: Element, seasonName: String): SEpisode { + private fun episodeFromElement(element: Element, seasonName: String) = SEpisode.create().apply { val episodeId = element.attr("data-id") val epNum = element.selectFirst("div.episode-number")!!.text() val epName = element.selectFirst("h3.film-name a")!!.text() - val episode = SEpisode.create().apply { - name = "$seasonName $epNum $epName" - setUrlWithoutDomain("$baseUrl/ajax/v2/episode/servers/$episodeId") - } - return episode + name = "$seasonName $epNum $epName" + episode_number = "${seasonName.getNumber()}.${epNum.getNumber().padStart(3, '0')}".toFloatOrNull() ?: 1F + setUrlWithoutDomain("$baseUrl/ajax/v2/episode/servers/$episodeId") } - private fun getNumberFromEpsString(epsStr: String): String { - return epsStr.filter { it.isDigit() } - } + private fun String.getNumber() = filter(Char::isDigit) // ============================ Video Links ============================= + private val extractor by lazy { DopeFlixExtractor(client) } override fun videoListParse(response: Response): List