diff --git a/src/en/gogoanime/build.gradle b/src/en/gogoanime/build.gradle new file mode 100644 index 00000000..9ac6d8eb --- /dev/null +++ b/src/en/gogoanime/build.gradle @@ -0,0 +1,14 @@ +ext { + extName = 'Gogoanime' + extClass = '.GogoAnime' + extVersionCode = 87 +} + +apply from: "$rootDir/common.gradle" + +dependencies { + implementation(project(':lib:streamwish-extractor')) + implementation(project(':lib:mp4upload-extractor')) + implementation(project(':lib:dood-extractor')) + implementation(project(':lib:gogostream-extractor')) +} \ No newline at end of file diff --git a/src/en/gogoanime/res/mipmap-hdpi/ic_launcher.png b/src/en/gogoanime/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..9e4d2f6b Binary files /dev/null and b/src/en/gogoanime/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/en/gogoanime/res/mipmap-mdpi/ic_launcher.png b/src/en/gogoanime/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..1bd68c8c Binary files /dev/null and b/src/en/gogoanime/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/en/gogoanime/res/mipmap-xhdpi/ic_launcher.png b/src/en/gogoanime/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..4f2d6d97 Binary files /dev/null and b/src/en/gogoanime/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/en/gogoanime/res/mipmap-xxhdpi/ic_launcher.png b/src/en/gogoanime/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..1a6056f7 Binary files /dev/null and b/src/en/gogoanime/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/en/gogoanime/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/gogoanime/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..67e41341 Binary files /dev/null and b/src/en/gogoanime/res/mipmap-xxxhdpi/ic_launcher.png differ 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 new file mode 100644 index 00000000..b05f103c --- /dev/null +++ b/src/en/gogoanime/src/eu/kanade/tachiyomi/animeextension/en/gogoanime/GogoAnime.kt @@ -0,0 +1,299 @@ +package eu.kanade.tachiyomi.animeextension.en.gogoanime + +import android.app.Application +import androidx.preference.ListPreference +import androidx.preference.MultiSelectListPreference +import androidx.preference.PreferenceScreen +import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource +import eu.kanade.tachiyomi.animesource.model.AnimeFilterList +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.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 +import eu.kanade.tachiyomi.util.asJsoup +import eu.kanade.tachiyomi.util.parallelCatchingFlatMapBlocking +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 + +class GogoAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() { + + override val name = "Gogoanime" + + // TODO: Check frequency of url changes to potentially + // add back overridable baseurl preference + override val baseUrl = "https://anitaku.to" + + override val lang = "en" + + override val supportsLatest = true + + override fun headersBuilder() = super.headersBuilder() + .add("Origin", baseUrl) + .add("Referer", "$baseUrl/") + + 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" + + override fun popularAnimeFromElement(element: Element): SAnime = SAnime.create().apply { + setUrlWithoutDomain(element.attr("href")) + thumbnail_url = element.selectFirst("img")!!.attr("src") + title = element.attr("title") + } + + override fun popularAnimeNextPageSelector(): String = "ul.pagination-list li:last-child:not(.selected)" + + // =============================== Latest =============================== + 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.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("$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) + } + } + + override fun searchAnimeSelector(): String = popularAnimeSelector() + + override fun searchAnimeFromElement(element: Element): SAnime = popularAnimeFromElement(element) + + 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.absUrl("href"), headers)).execute().asJsoup() + } ?: document + + return SAnime.create().apply { + title = infoDocument.selectFirst("div.anime_info_body_bg > h1")!!.text() + genre = infoDocument.getInfo("Genre:") + status = parseStatus(infoDocument.getInfo("Status:").orEmpty()) + + description = buildString { + val summary = infoDocument.selectFirst("div.anime_info_body_bg > div.description") + append(summary?.text()) + + // 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("$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) + } + + override fun episodeListParse(response: Response): List { + val document = response.asJsoup() + val totalEpisodes = document.select(episodeListSelector()).last()!!.attr("ep_end") + val id = document.select("input#movie_id").attr("value") + return episodesRequest(totalEpisodes, id) + } + + override fun episodeListSelector() = "ul#episode_page li a" + + override fun episodeFromElement(element: Element): SEpisode { + val ep = element.selectFirst("div.name")!!.ownText().substringAfter(" ") + return SEpisode.create().apply { + setUrlWithoutDomain(element.attr("abs:href")) + episode_number = ep.toFloat() + name = "Episode $ep" + } + } + + // ============================ Video Links ============================= + 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) } + + override fun videoListParse(response: Response): List