From 9585aec265a429e23456ed5ce8a77c984d73f9ea Mon Sep 17 00:00:00 2001 From: Secozzi <49240133+Secozzi@users.noreply.github.com> Date: Fri, 22 Sep 2023 10:15:46 +0000 Subject: [PATCH] feat(en/aniwave): Code refactor + small new features (#2237) --- src/en/aniwave/build.gradle | 14 +- .../animeextension/en/nineanime/Aniwave.kt | 382 +++++++----------- .../animeextension/en/nineanime/AniwaveDto.kt | 60 +++ .../en/nineanime/AniwaveUtils.kt | 55 +++ .../animeextension/en/nineanime/JSONUtil.kt | 86 ---- .../nineanime/extractors/VidsrcExtractor.kt | 67 +++ 6 files changed, 340 insertions(+), 324 deletions(-) create mode 100644 src/en/aniwave/src/eu/kanade/tachiyomi/animeextension/en/nineanime/AniwaveDto.kt create mode 100644 src/en/aniwave/src/eu/kanade/tachiyomi/animeextension/en/nineanime/AniwaveUtils.kt delete mode 100644 src/en/aniwave/src/eu/kanade/tachiyomi/animeextension/en/nineanime/JSONUtil.kt create mode 100644 src/en/aniwave/src/eu/kanade/tachiyomi/animeextension/en/nineanime/extractors/VidsrcExtractor.kt diff --git a/src/en/aniwave/build.gradle b/src/en/aniwave/build.gradle index 9c35f78fd..a9adedd1e 100644 --- a/src/en/aniwave/build.gradle +++ b/src/en/aniwave/build.gradle @@ -1,20 +1,22 @@ -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply plugin: 'kotlinx-serialization' +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.serialization) +} ext { extName = 'Aniwave' pkgNameSuffix = 'en.nineanime' extClass = '.Aniwave' - extVersionCode = 49 + extVersionCode = 50 libVersion = '13' } dependencies { implementation(project(':lib-filemoon-extractor')) implementation(project(':lib-mp4upload-extractor')) - implementation (project(':lib-streamtape-extractor')) - implementation "dev.datlag.jsunpacker:jsunpacker:1.0.1" + implementation(project(':lib-streamtape-extractor')) + implementation(project(':lib-playlist-utils')) } apply from: "$rootDir/common.gradle" diff --git a/src/en/aniwave/src/eu/kanade/tachiyomi/animeextension/en/nineanime/Aniwave.kt b/src/en/aniwave/src/eu/kanade/tachiyomi/animeextension/en/nineanime/Aniwave.kt index 5f32ceaba..9c147e0d1 100644 --- a/src/en/aniwave/src/eu/kanade/tachiyomi/animeextension/en/nineanime/Aniwave.kt +++ b/src/en/aniwave/src/eu/kanade/tachiyomi/animeextension/en/nineanime/Aniwave.kt @@ -2,47 +2,38 @@ package eu.kanade.tachiyomi.animeextension.en.nineanime import android.app.Application import android.content.SharedPreferences +import android.widget.Toast import androidx.preference.ListPreference import androidx.preference.MultiSelectListPreference import androidx.preference.PreferenceScreen import androidx.preference.SwitchPreferenceCompat +import eu.kanade.tachiyomi.animeextension.en.nineanime.extractors.VidsrcExtractor 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 import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource import eu.kanade.tachiyomi.lib.filemoonextractor.FilemoonExtractor import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor import eu.kanade.tachiyomi.lib.streamtapeextractor.StreamTapeExtractor import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -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.Serializable import kotlinx.serialization.json.Json -import kotlinx.serialization.json.JsonObject -import kotlinx.serialization.json.jsonPrimitive -import okhttp3.FormBody -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response -import org.jsoup.Jsoup import org.jsoup.nodes.Document import org.jsoup.nodes.Element -import rx.Observable import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy +import java.text.SimpleDateFormat +import java.util.Locale class Aniwave : ConfigurableAnimeSource, ParsedAnimeHttpSource() { @@ -50,8 +41,9 @@ class Aniwave : ConfigurableAnimeSource, ParsedAnimeHttpSource() { override val id: Long = 98855593379717478 - override val baseUrl - get() = preferences.getString(PREF_DOMAIN_KEY, PREF_DOMAIN_DEFAULT)!! + override val baseUrl by lazy { + preferences.getString(PREF_DOMAIN_KEY, PREF_DOMAIN_DEFAULT)!! + } override val lang = "en" @@ -61,35 +53,36 @@ class Aniwave : ConfigurableAnimeSource, ParsedAnimeHttpSource() { private val json: Json by injectLazy() + private val utils by lazy { AniwaveUtils(client, headers) } + private val preferences: SharedPreferences by lazy { Injekt.get().getSharedPreferences("source_$id", 0x0000) } - override fun headersBuilder() = super.headersBuilder() - .add("Referer", "$baseUrl/") + private val refererHeaders = headers.newBuilder().apply { + add("Referer", "$baseUrl/") + }.build() // ============================== Popular =============================== - override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/filter?sort=trending&page=$page") + override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/filter?sort=trending&page=$page", refererHeaders) override fun popularAnimeSelector(): String = "div.ani.items > div.item" override fun popularAnimeFromElement(element: Element) = SAnime.create().apply { - setUrlWithoutDomain( - element.select("a.name") - .attr("href") - .substringBefore("?"), - ) + element.select("a.name").let { a -> + setUrlWithoutDomain(a.attr("href").substringBefore("?")) + title = a.text() + } thumbnail_url = element.select("div.poster img").attr("src") - title = element.select("a.name").text() } override fun popularAnimeNextPageSelector(): String = - "nav > ul.pagination > li > a[rel=next]" // TODO The last 2 pages will be ignored, need to override fetchPopular to fix + "nav > ul.pagination > li.active ~ li" // =============================== Latest =============================== - override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/filter?sort=recently_updated&page=$page") + override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/filter?sort=recently_updated&page=$page", refererHeaders) override fun latestUpdatesSelector(): String = popularAnimeSelector() @@ -99,20 +92,10 @@ class Aniwave : ConfigurableAnimeSource, ParsedAnimeHttpSource() { // =============================== Search =============================== - override fun fetchSearchAnime(page: Int, query: String, filters: AnimeFilterList): Observable { - val params = AniwaveFilters.getSearchParameters(filters) - return client.newCall(searchAnimeRequest(page, query, params)) - .asObservableSuccess() - .map { response -> - searchAnimeParse(response) - } - } + override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { + val filters = AniwaveFilters.getSearchParameters(filters) - override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request = - throw Exception("Not used") - - private fun searchAnimeRequest(page: Int, query: String, filters: AniwaveFilters.FilterSearchParams): Request { - val vrf = if (query.isNotBlank()) callEnimax(query, "vrf") else "" + val vrf = if (query.isNotBlank()) utils.callEnimax(query, "vrf") else "" var url = "$baseUrl/filter?keyword=$query" if (filters.genre.isNotBlank()) url += filters.genre @@ -124,10 +107,7 @@ class Aniwave : ConfigurableAnimeSource, ParsedAnimeHttpSource() { if (filters.language.isNotBlank()) url += filters.language if (filters.rating.isNotBlank()) url += filters.rating - return GET( - "$url&sort=${filters.sort}&page=$page&$vrf", - headers = Headers.headersOf("Referer", "$baseUrl/"), - ) + return GET("$url&sort=${filters.sort}&page=$page&$vrf", refererHeaders) } override fun searchAnimeSelector(): String = popularAnimeSelector() @@ -165,19 +145,23 @@ class Aniwave : ConfigurableAnimeSource, ParsedAnimeHttpSource() { override fun episodeListRequest(anime: SAnime): Request { val id = client.newCall(GET(baseUrl + anime.url)).execute().asJsoup() .selectFirst("div[data-id]")!!.attr("data-id") - val vrf = callEnimax(id, "vrf") - return GET( - "$baseUrl/ajax/episode/list/$id?$vrf", - headers = Headers.headersOf("url", anime.url), - ) + val vrf = utils.callEnimax(id, "vrf") + + val listHeaders = headers.newBuilder().apply { + add("Accept", "application/json, text/javascript, */*; q=0.01") + add("Referer", baseUrl + anime.url) + add("X-Requested-With", "XMLHttpRequest") + }.build() + + return GET("$baseUrl/ajax/episode/list/$id?$vrf#${anime.url}", listHeaders) } override fun episodeListSelector() = "div.episodes ul > li > a" override fun episodeListParse(response: Response): List { - val animeUrl = response.request.header("url").toString() - val responseObject = json.decodeFromString(response.body.string()) - val document = Jsoup.parse(JSONUtil.unescape(responseObject["result"]!!.jsonPrimitive.content)) + val animeUrl = response.request.url.fragment!! + val document = response.parseAs().toDocument() + val episodeElements = document.select(episodeListSelector()) return episodeElements.parallelMap { episodeFromElements(it, animeUrl) }.reversed() } @@ -185,10 +169,14 @@ class Aniwave : ConfigurableAnimeSource, ParsedAnimeHttpSource() { override fun episodeFromElement(element: Element): SEpisode = throw Exception("not Used") private fun episodeFromElements(element: Element, url: String): SEpisode { + val title = element.parent()?.attr("title") ?: "" + val epNum = element.attr("data-num") val ids = element.attr("data-ids") - val sub = element.attr("data-sub").toInt().toBoolean() - val dub = element.attr("data-dub").toInt().toBoolean() + val sub = if (element.attr("data-sub").toInt().toBoolean()) "Sub" else "" + val dub = if (element.attr("data-dub").toInt().toBoolean()) "Dub" else "" + val softSub = if (SOFTSUB_REGEX.find(title) != null) "SoftSub" else "" + val extraInfo = if (element.hasClass("filler") && preferences.getBoolean(PREF_MARK_FILLERS_KEY, PREF_MARK_FILLERS_DEFAULT)) { " • Filler Episode" } else { @@ -202,7 +190,10 @@ class Aniwave : ConfigurableAnimeSource, ParsedAnimeHttpSource() { if (name.isNotEmpty() && name != namePrefix) ": $name" else "" this.url = "$ids&epurl=$url/ep-$epNum" episode_number = epNum.toFloat() - scanlator = ((if (sub) "Sub" else "") + if (dub) ", Dub" else "") + extraInfo + date_upload = RELEASE_REGEX.find(title)?.let { + parseDate(it.groupValues[1]) + } ?: 0L + scanlator = arrayOf(sub, softSub, dub).filter(String::isNotBlank).joinToString(", ") + extraInfo } } @@ -210,25 +201,40 @@ class Aniwave : ConfigurableAnimeSource, ParsedAnimeHttpSource() { override fun videoListRequest(episode: SEpisode): Request { val ids = episode.url.substringBefore("&") - val vrf = callEnimax(ids, "vrf") + val vrf = utils.callEnimax(ids, "vrf") val url = "/ajax/server/list/$ids?$vrf" val epurl = episode.url.substringAfter("epurl=") - return GET(baseUrl + url, headers = Headers.headersOf("url", epurl)) + + val listHeaders = headers.newBuilder().apply { + add("Accept", "application/json, text/javascript, */*; q=0.01") + add("Referer", baseUrl + epurl) + add("X-Requested-With", "XMLHttpRequest") + }.build() + + return GET("$baseUrl$url#$epurl", listHeaders) } + data class VideoData( + val type: String, + val serverId: String, + val serverName: String, + ) + override fun videoListParse(response: Response): List