diff --git a/src/en/gogoanime/build.gradle b/src/en/gogoanime/build.gradle index d21ed2388..a053482fc 100644 --- a/src/en/gogoanime/build.gradle +++ b/src/en/gogoanime/build.gradle @@ -7,7 +7,7 @@ ext { extName = 'Gogoanime' pkgNameSuffix = 'en.gogoanime' extClass = '.GogoAnime' - extVersionCode = 76 + extVersionCode = 77 libVersion = '13' } @@ -15,8 +15,7 @@ dependencies { implementation(project(':lib-streamwish-extractor')) implementation(project(':lib-mp4upload-extractor')) implementation(project(':lib-dood-extractor')) - implementation(project(':lib-playlist-utils')) - implementation "dev.datlag.jsunpacker:jsunpacker:1.0.1" + implementation(project(':lib-gogostream-extractor')) } apply from: "$rootDir/common.gradle" diff --git a/src/en/gogoanime/src/eu/kanade/tachiyomi/animeextension/en/gogoanime/GogoAnime.kt b/src/en/gogoanime/src/eu/kanade/tachiyomi/animeextension/en/gogoanime/GogoAnime.kt index ab0d0800a..316e51eae 100644 --- a/src/en/gogoanime/src/eu/kanade/tachiyomi/animeextension/en/gogoanime/GogoAnime.kt +++ b/src/en/gogoanime/src/eu/kanade/tachiyomi/animeextension/en/gogoanime/GogoAnime.kt @@ -1,14 +1,12 @@ package eu.kanade.tachiyomi.animeextension.en.gogoanime import android.app.Application -import android.content.SharedPreferences import android.widget.Toast import androidx.preference.EditTextPreference import androidx.preference.ListPreference import androidx.preference.MultiSelectListPreference import androidx.preference.PreferenceScreen import eu.kanade.tachiyomi.animeextension.BuildConfig -import eu.kanade.tachiyomi.animeextension.en.gogoanime.extractors.GogoCdnExtractor import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource import eu.kanade.tachiyomi.animesource.model.AnimeFilterList import eu.kanade.tachiyomi.animesource.model.SAnime @@ -16,6 +14,7 @@ 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.doodextractor.DoodExtractor +import eu.kanade.tachiyomi.lib.gogostreamextractor.GogoStreamExtractor import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor import eu.kanade.tachiyomi.lib.streamwishextractor.StreamWishExtractor import eu.kanade.tachiyomi.network.GET @@ -24,19 +23,14 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.runBlocking -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.json.Json -import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response import org.jsoup.nodes.Document import org.jsoup.nodes.Element import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -import uy.kohesive.injekt.injectLazy import java.lang.Exception -@ExperimentalSerializationApi class GogoAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() { override val name = "Gogoanime" @@ -47,16 +41,17 @@ class GogoAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() { override val supportsLatest = true - override val client: OkHttpClient = network.cloudflareClient + override val client = network.cloudflareClient - private val json: Json by injectLazy() + override fun headersBuilder() = super.headersBuilder() + .add("Origin", baseUrl) + .add("Referer", "$baseUrl/") - private val preferences: SharedPreferences by lazy { + private val preferences by lazy { Injekt.get().getSharedPreferences("source_$id", 0x0000) } // ============================== Popular =============================== - override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/popular.html?page=$page", headers) override fun popularAnimeSelector(): String = "div.img a" @@ -70,44 +65,28 @@ class GogoAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() { override fun popularAnimeNextPageSelector(): String = "ul.pagination-list li:last-child:not(.selected)" // =============================== Latest =============================== - - override fun latestUpdatesRequest(page: Int): Request = - GET("$baseUrl/?page=$page", headers) + override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/home.html?page=$page", headers) override fun latestUpdatesSelector(): String = "div.img a" - override fun latestUpdatesFromElement(element: Element): SAnime { - val imgUrl = element.selectFirst("img")!!.attr("src") - - val newUrl = imgUrl.replaceFirst("https://", "").substringAfter("/").replaceFirst("cover", "/category").substringBeforeLast('.') - - val finalUrl = newUrl.let { url -> - url.lastIndexOf('-').let { lastIndex -> - val suffix = url.substring(lastIndex + 1) - if (lastIndex == -1 || !suffix.all { it.isDigit() } || suffix.length < 3) { - newUrl - } else { - url.substring(0, lastIndex) - } - } - } - return SAnime.create().apply { - setUrlWithoutDomain(finalUrl) - thumbnail_url = element.selectFirst("img")!!.attr("src") - title = element.attr("title") - } + override fun latestUpdatesFromElement(element: Element) = SAnime.create().apply { + thumbnail_url = element.selectFirst("img")?.attr("src") + title = element.attr("title") + val slug = element.attr("href").substringAfter(baseUrl) + .trimStart('/') + .substringBefore("-episode-") + setUrlWithoutDomain("/category/$slug") } override fun latestUpdatesNextPageSelector(): String = popularAnimeNextPageSelector() // =============================== Search =============================== - override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { val params = GogoAnimeFilters.getSearchParameters(filters) return when { params.genre.isNotEmpty() -> GET("$baseUrl/genre/${params.genre}?page=$page", headers) - params.recent.isNotEmpty() -> GET("https://ajax.gogo-load.com/ajax/page-recent-release.html?page=$page&type=${params.recent}", headers) + params.recent.isNotEmpty() -> GET("$AJAX_URL/page-recent-release.html?page=$page&type=${params.recent}", headers) params.season.isNotEmpty() -> GET("$baseUrl/${params.season}?page=$page", headers) else -> GET("$baseUrl/filter.html?keyword=$query&${params.filter}&page=$page", headers) } @@ -120,42 +99,37 @@ class GogoAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() { override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector() // ============================== Filters =============================== - override fun getFilterList(): AnimeFilterList = GogoAnimeFilters.FILTER_LIST // =========================== Anime Details ============================ - override fun animeDetailsParse(document: Document): SAnime { val infoDocument = document.selectFirst("div.anime-info a[href]")?.let { - client.newCall(GET(it.attr("abs:href"), headers)).execute().asJsoup() + client.newCall(GET(it.absUrl("href"), headers)).execute().asJsoup() } ?: document return SAnime.create().apply { - title = infoDocument.select("div.anime_info_body_bg h1").text() - genre = infoDocument.select("p.type:eq(5) a").joinToString("") { it.text() } - description = infoDocument.selectFirst("p.type:eq(4)")!!.ownText() - status = parseStatus(infoDocument.select("p.type:eq(7) a").text()) + title = infoDocument.selectFirst("div.anime_info_body_bg > h1")!!.text() + genre = infoDocument.getInfo("Genre:") + status = parseStatus(infoDocument.getInfo("Status:").orEmpty()) - // add alternative name to anime description - val altName = "Other name(s): " - infoDocument.selectFirst("p.type:eq(8)")?.ownText()?.let { - if (it.isBlank().not()) { - description = when { - description.isNullOrBlank() -> altName + it - else -> description + "\n\n$altName" + it - } + description = buildString { + infoDocument.getInfo("Plot Summary:")?.also(::append) + + // add alternative name to anime description + infoDocument.getInfo("Other name:")?.also { + if (isNotBlank()) append("\n\n") + append("Other name(s): $it") } } } } // ============================== Episodes ============================== - private fun episodesRequest(totalEpisodes: String, id: String): List { - val request = GET("https://ajax.gogo-load.com/ajax/load-list-episode?ep_start=0&ep_end=$totalEpisodes&id=$id", headers) + val request = GET("$AJAX_URL/load-list-episode?ep_start=0&ep_end=$totalEpisodes&id=$id", headers) val epResponse = client.newCall(request).execute() val document = epResponse.asJsoup() - return document.select("a").map { episodeFromElement(it) } + return document.select("a").map(::episodeFromElement) } override fun episodeListParse(response: Response): List { @@ -177,8 +151,7 @@ class GogoAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() { } // ============================ Video Links ============================= - - private val gogoExtractor by lazy { GogoCdnExtractor(client, json) } + private val gogoExtractor by lazy { GogoStreamExtractor(client) } private val streamwishExtractor by lazy { StreamWishExtractor(client, headers) } private val doodExtractor by lazy { DoodExtractor(client) } private val mp4uploadExtractor by lazy { Mp4uploadExtractor(client) } @@ -187,17 +160,15 @@ class GogoAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() { val document = response.asJsoup() val hosterSelection = preferences.getStringSet(PREF_HOSTER_KEY, PREF_HOSTER_DEFAULT)!! - return document.select("div.anime_muti_link > ul > li").parallelMap { server -> - runCatching { - val className = server.className() - if (!hosterSelection.contains(className)) return@runCatching emptyList() - val serverUrl = server.selectFirst("a") - ?.attr("abs:data-video") - ?: return@runCatching emptyList() + return document.select("div.anime_muti_link > ul > li").parallelCatchingFlatMap { server -> + val className = server.className() + if (!hosterSelection.contains(className)) return@parallelCatchingFlatMap emptyList() + val serverUrl = server.selectFirst("a") + ?.attr("abs:data-video") + ?: return@parallelCatchingFlatMap emptyList() - getHosterVideos(className, serverUrl) - }.getOrElse { emptyList() } - }.flatten().sort().ifEmpty { throw Exception("Failed to extract videos") } + getHosterVideos(className, serverUrl) + } } private fun getHosterVideos(className: String, serverUrl: String): List