feat(en/gogoanime): Add advanced search, new video hoster & more (#2017)

This commit is contained in:
Secozzi
2023-08-06 00:17:14 +02:00
committed by GitHub
parent cfc7ff07bd
commit c7c6f4403f
4 changed files with 492 additions and 211 deletions

View File

@ -1,18 +1,20 @@
apply plugin: 'com.android.application' plugins {
apply plugin: 'kotlin-android' alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
}
ext { ext {
extName = 'Gogoanime' extName = 'Gogoanime'
pkgNameSuffix = 'en.gogoanime' pkgNameSuffix = 'en.gogoanime'
extClass = '.GogoAnime' extClass = '.GogoAnime'
extVersionCode = 68 extVersionCode = 69
libVersion = '13' libVersion = '13'
} }
dependencies { dependencies {
implementation(project(':lib-mp4upload-extractor')) implementation(project(':lib-mp4upload-extractor'))
implementation(project(':lib-streamsb-extractor'))
implementation(project(':lib-dood-extractor')) implementation(project(':lib-dood-extractor'))
implementation(project(':lib-playlist-utils'))
implementation "dev.datlag.jsunpacker:jsunpacker:1.0.1" implementation "dev.datlag.jsunpacker:jsunpacker:1.0.1"
} }

View File

@ -2,13 +2,15 @@ package eu.kanade.tachiyomi.animeextension.en.gogoanime
import android.app.Application import android.app.Application
import android.content.SharedPreferences import android.content.SharedPreferences
import android.widget.Toast
import androidx.preference.EditTextPreference import androidx.preference.EditTextPreference
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.MultiSelectListPreference import androidx.preference.MultiSelectListPreference
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.AppInfo
import eu.kanade.tachiyomi.animeextension.en.gogoanime.extractors.GogoCdnExtractor import eu.kanade.tachiyomi.animeextension.en.gogoanime.extractors.GogoCdnExtractor
import eu.kanade.tachiyomi.animeextension.en.gogoanime.extractors.StreamWishExtractor
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
import eu.kanade.tachiyomi.animesource.model.SAnime import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.animesource.model.SEpisode import eu.kanade.tachiyomi.animesource.model.SEpisode
@ -16,7 +18,6 @@ import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor import eu.kanade.tachiyomi.lib.doodextractor.DoodExtractor
import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor import eu.kanade.tachiyomi.lib.mp4uploadextractor.Mp4uploadExtractor
import eu.kanade.tachiyomi.lib.streamsbextractor.StreamSBExtractor
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -56,7 +57,7 @@ class GogoAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
// ============================== Popular =============================== // ============================== Popular ===============================
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/popular.html?page=$page") override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/popular.html?page=$page", headers)
override fun popularAnimeSelector(): String = "div.img a" override fun popularAnimeSelector(): String = "div.img a"
@ -87,17 +88,13 @@ class GogoAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
// =============================== Search =============================== // =============================== Search ===============================
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
val filterList = if (filters.isEmpty()) getFilterList() else filters val params = GogoAnimeFilters.getSearchParameters(filters)
val genreFilter = filterList.find { it is GenreFilter } as GenreFilter
val recentFilter = filterList.find { it is RecentFilter } as RecentFilter
val seasonFilter = filterList.find { it is SeasonFilter } as SeasonFilter
return when { return when {
query.isNotBlank() -> GET("$baseUrl/search.html?keyword=$query&page=$page", headers) params.genre.isNotEmpty() -> GET("$baseUrl/genre/${params.genre}?page=$page", headers)
genreFilter.state != 0 -> GET("$baseUrl/genre/${genreFilter.toUriPart()}?page=$page") params.recent.isNotEmpty() -> GET("https://ajax.gogo-load.com/ajax/page-recent-release.html?page=$page&type=${params.recent}", headers)
recentFilter.state != 0 -> GET("https://ajax.gogo-load.com/ajax/page-recent-release.html?page=$page&type=${recentFilter.toUriPart()}") params.season.isNotEmpty() -> GET("$baseUrl/${params.season}?page=$page", headers)
seasonFilter.state != 0 -> GET("$baseUrl/${seasonFilter.toUriPart()}?page=$page", headers) else -> GET("$baseUrl/filter.html?keyword=$query&${params.filter}&page=$page", headers)
else -> GET("$baseUrl/popular.html?page=$page")
} }
} }
@ -107,6 +104,10 @@ class GogoAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector() override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector()
// ============================== Filters ===============================
override fun getFilterList(): AnimeFilterList = GogoAnimeFilters.FILTER_LIST
// =========================== Anime Details ============================ // =========================== Anime Details ============================
override fun animeDetailsParse(document: Document): SAnime { override fun animeDetailsParse(document: Document): SAnime {
@ -138,8 +139,6 @@ class GogoAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
return document.select("a").map { episodeFromElement(it) } return document.select("a").map { episodeFromElement(it) }
} }
override fun episodeListSelector() = "ul#episode_page li a"
override fun episodeListParse(response: Response): List<SEpisode> { override fun episodeListParse(response: Response): List<SEpisode> {
val document = response.asJsoup() val document = response.asJsoup()
val totalEpisodes = document.select(episodeListSelector()).last()!!.attr("ep_end") val totalEpisodes = document.select(episodeListSelector()).last()!!.attr("ep_end")
@ -147,6 +146,8 @@ class GogoAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
return episodesRequest(totalEpisodes, id) return episodesRequest(totalEpisodes, id)
} }
override fun episodeListSelector() = "ul#episode_page li a"
override fun episodeFromElement(element: Element): SEpisode { override fun episodeFromElement(element: Element): SEpisode {
val ep = element.selectFirst("div.name")!!.ownText().substringAfter(" ") val ep = element.selectFirst("div.name")!!.ownText().substringAfter(" ")
return SEpisode.create().apply { return SEpisode.create().apply {
@ -158,45 +159,40 @@ class GogoAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
// ============================ Video Links ============================= // ============================ Video Links =============================
private val gogoExtractor by lazy { GogoCdnExtractor(client, json) }
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<Video> { override fun videoListParse(response: Response): List<Video> {
val document = response.asJsoup() val document = response.asJsoup()
val gogoExtractor = GogoCdnExtractor(client, json)
val hosterSelection = preferences.getStringSet(PREF_HOSTER_KEY, PREF_HOSTER_DEFAULT)!! val hosterSelection = preferences.getStringSet(PREF_HOSTER_KEY, PREF_HOSTER_DEFAULT)!!
val videoList = mutableListOf<Video>() 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()
videoList.addAll( getHosterVideos(className, serverUrl)
document.select("div.anime_muti_link > ul > li").parallelMap { server -> }.getOrElse { emptyList() }
runCatching { }.flatten().sort().ifEmpty { throw Exception("Failed to extract videos") }
val className = server.className() }
if (!hosterSelection.contains(className)) return@runCatching null
val serverUrl = server.selectFirst("a")
?.attr("data-video")
?.replace(Regex("^//"), "https://")
?: return@runCatching null
when (className) {
"anime" -> {
gogoExtractor.videosFromUrl(serverUrl)
}
"vidcdn" -> {
gogoExtractor.videosFromUrl(serverUrl)
}
"streamsb" -> {
StreamSBExtractor(client).videosFromUrl(serverUrl, headers)
}
"doodstream" -> {
DoodExtractor(client).videosFromUrl(serverUrl)
}
"mp4upload" -> {
Mp4uploadExtractor(client).videosFromUrl(serverUrl, headers)
}
else -> null
}
}.getOrNull()
}.filterNotNull().flatten(),
)
return videoList.sort() private fun getHosterVideos(className: String, serverUrl: String): List<Video> {
return when (className) {
"anime" -> gogoExtractor.videosFromUrl(serverUrl)
"vidcdn" -> gogoExtractor.videosFromUrl(serverUrl)
"streamwish" -> streamwishExtractor.videosFromUrl(serverUrl)
"doodstream" -> doodExtractor.videosFromUrl(serverUrl)
"mp4upload" -> mp4uploadExtractor.videosFromUrl(serverUrl, headers)
"filelions" -> {
streamwishExtractor.videosFromUrl(serverUrl, videoNameGen = { quality -> "FileLions - $quality" })
}
else -> emptyList()
}
} }
override fun videoListSelector() = throw Exception("not used") override fun videoListSelector() = throw Exception("not used")
@ -239,20 +235,23 @@ class GogoAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
"Gogostream", "Gogostream",
"Vidstreaming", "Vidstreaming",
"Doodstream", "Doodstream",
"StreamSB", "StreamWish",
"Mp4upload", "Mp4upload",
"FileLions",
) )
private val HOSTERS_NAMES = arrayOf( // Names that appears in the gogo html private val HOSTERS_NAMES = arrayOf( // Names that appears in the gogo html
"vidcdn", "vidcdn",
"anime", "anime",
"doodstream", "doodstream",
"streamsb", "streamwish",
"mp4upload", "mp4upload",
"filelions",
) )
private const val PREF_DOMAIN_KEY = "preferred_domain_name" private val PREF_DOMAIN_KEY = "preferred_domain_name_v${AppInfo.getVersionName()}"
private const val PREF_DOMAIN_TITLE = "Override BaseUrl" private const val PREF_DOMAIN_TITLE = "Override BaseUrl"
private const val PREF_DOMAIN_DEFAULT = "https://gogoanime.hu" private const val PREF_DOMAIN_DEFAULT = "https://gogoanime3.net"
private const val PREF_DOMAIN_SUMMARY = "For temporary uses. Updating the extension will erase this setting."
private const val PREF_QUALITY_KEY = "preferred_quality" private const val PREF_QUALITY_KEY = "preferred_quality"
private const val PREF_QUALITY_DEFAULT = "1080" private const val PREF_QUALITY_DEFAULT = "1080"
@ -270,13 +269,14 @@ class GogoAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
EditTextPreference(screen.context).apply { EditTextPreference(screen.context).apply {
key = PREF_DOMAIN_KEY key = PREF_DOMAIN_KEY
title = PREF_DOMAIN_TITLE title = PREF_DOMAIN_TITLE
summary = "Override default domain (requires app restart)" summary = PREF_DOMAIN_SUMMARY
dialogTitle = PREF_DOMAIN_TITLE dialogTitle = PREF_DOMAIN_TITLE
dialogMessage = "Default: $PREF_DOMAIN_DEFAULT" dialogMessage = "Default: $PREF_DOMAIN_DEFAULT"
setDefaultValue(PREF_DOMAIN_DEFAULT) setDefaultValue(PREF_DOMAIN_DEFAULT)
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
val newValueString = newValue as String val newValueString = newValue as String
Toast.makeText(screen.context, "Restart Aniyomi to apply new setting.", Toast.LENGTH_LONG).show()
preferences.edit().putString(key, newValueString.trim()).commit() preferences.edit().putString(key, newValueString.trim()).commit()
} }
}.also(screen::addPreference) }.also(screen::addPreference)
@ -326,159 +326,4 @@ class GogoAnime : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
} }
}.also(screen::addPreference) }.also(screen::addPreference)
} }
// ============================== Filters ===============================
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
AnimeFilter.Header("Text search ignores filters"),
GenreFilter(),
RecentFilter(),
SeasonFilter(),
)
private class GenreFilter : UriPartFilter(
"Genres",
arrayOf(
Pair("<select>", ""),
Pair("Action", "action"),
Pair("Adult Cast", "adult-cast"),
Pair("Adventure", "adventure"),
Pair("Anthropomorphic", "anthropomorphic"),
Pair("Avant Garde", "avant-garde"),
Pair("Boys Love", "shounen-ai"),
Pair("Cars", "cars"),
Pair("CGDCT", "cgdct"),
Pair("Childcare", "childcare"),
Pair("Comedy", "comedy"),
Pair("Comic", "comic"),
Pair("Crime", "crime"),
Pair("Crossdressing", "crossdressing"),
Pair("Delinquents", "delinquents"),
Pair("Dementia", "dementia"),
Pair("Demons", "demons"),
Pair("Detective", "detective"),
Pair("Drama", "drama"),
Pair("Dub", "dub"),
Pair("Ecchi", "ecchi"),
Pair("Erotica", "erotica"),
Pair("Family", "family"),
Pair("Fantasy", "fantasy"),
Pair("Gag Humor", "gag-humor"),
Pair("Game", "game"),
Pair("Gender Bender", "gender-bender"),
Pair("Gore", "gore"),
Pair("Gourmet", "gourmet"),
Pair("Harem", "harem"),
Pair("Hentai", "hentai"),
Pair("High Stakes Game", "high-stakes-game"),
Pair("Historical", "historical"),
Pair("Horror", "horror"),
Pair("Isekai", "isekai"),
Pair("Iyashikei", "iyashikei"),
Pair("Josei", "josei"),
Pair("Kids", "kids"),
Pair("Magic", "magic"),
Pair("Magical Sex Shift", "magical-sex-shift"),
Pair("Mahou Shoujo", "mahou-shoujo"),
Pair("Martial Arts", "martial-arts"),
Pair("Mecha", "mecha"),
Pair("Medical", "medical"),
Pair("Military", "military"),
Pair("Music", "music"),
Pair("Mystery", "mystery"),
Pair("Mythology", "mythology"),
Pair("Organized Crime", "organized-crime"),
Pair("Parody", "parody"),
Pair("Performing Arts", "performing-arts"),
Pair("Pets", "pets"),
Pair("Police", "police"),
Pair("Psychological", "psychological"),
Pair("Reincarnation", "reincarnation"),
Pair("Romance", "romance"),
Pair("Romantic Subtext", "romantic-subtext"),
Pair("Samurai", "samurai"),
Pair("School", "school"),
Pair("Sci-Fi", "sci-fi"),
Pair("Seinen", "seinen"),
Pair("Shoujo", "shoujo"),
Pair("Shoujo Ai", "shoujo-ai"),
Pair("Shounen", "shounen"),
Pair("Shounen Ai", "shounen-ai"),
Pair("Slice of Life", "slice-of-life"),
Pair("Space", "space"),
Pair("Sports", "sports"),
Pair("Strategy Game", "strategy-game"),
Pair("Super Power", "super-power"),
Pair("Supernatural", "supernatural"),
Pair("Suspense", "suspense"),
Pair("Team Sports", "team-sports"),
Pair("Thriller", "thriller"),
Pair("Time Travel", "time-travel"),
Pair("Vampire", "vampire"),
Pair("Work Life", "work-life"),
Pair("Workplace", "workplace"),
Pair("Yaoi", "yaoi"),
Pair("Yuri", "yuri"),
),
)
private class RecentFilter : UriPartFilter(
"Recent Episodes",
arrayOf(
Pair("<select>", ""),
Pair("Recent Release", "1"),
Pair("Recent Dub", "2"),
Pair("Recent Chinese", "3"),
),
)
private class SeasonFilter : UriPartFilter(
"Season",
arrayOf(
Pair("<select>", ""),
Pair("Latest season", "new-season.html"),
Pair("Winter 2023", "sub-category/winter-2023-anime"),
Pair("Fall 2022", "sub-category/fall-2022-anime"),
Pair("Summer 2022", "sub-category/summer-2022-anime"),
Pair("Spring 2022", "sub-category/spring-2022-anime"),
Pair("Winter 2022", "sub-category/winter-2022-anime"),
Pair("Fall 2021", "sub-category/fall-2021-anime"),
Pair("Summer 2021", "sub-category/summer-2021-anime"),
Pair("Spring 2021", "sub-category/spring-2021-anime"),
Pair("Winter 2021", "sub-category/winter-2021-anime"),
Pair("Fall 2020", "sub-category/fall-2020-anime"),
Pair("Summer 2020", "sub-category/summer-2020-anime"),
Pair("Spring 2020", "sub-category/spring-2020-anime"),
Pair("Winter 2020", "sub-category/winter-2020-anime"),
Pair("Fall 2019", "sub-category/fall-2019-anime"),
Pair("Summer 2019", "sub-category/summer-2019-anime"),
Pair("Spring 2019", "sub-category/spring-2019-anime"),
Pair("Winter 2019", "sub-category/winter-2019-anime"),
Pair("Fall 2018", "sub-category/fall-2018-anime"),
Pair("Summer 2018", "sub-category/summer-2018-anime"),
Pair("Spring 2018", "sub-category/spring-2018-anime"),
Pair("Winter 2018", "sub-category/winter-2018-anime"),
Pair("Fall 2017", "sub-category/fall-2017-anime"),
Pair("Summer 2017", "sub-category/summer-2017-anime"),
Pair("Spring 2017", "sub-category/spring-2017-anime"),
Pair("Winter 2017", "sub-category/winter-2017-anime"),
Pair("Fall 2016", "sub-category/fall-2016-anime"),
Pair("Summer 2016", "sub-category/summer-2016-anime"),
Pair("Spring 2016", "sub-category/spring-2016-anime"),
Pair("Winter 2016", "sub-category/winter-2016-anime"),
Pair("Fall 2015", "sub-category/fall-2015-anime"),
Pair("Summer 2015", "sub-category/summer-2015-anime"),
Pair("Spring 2015", "sub-category/spring-2015-anime"),
Pair("Winter 2015", "sub-category/winter-2015-anime"),
Pair("Fall 2014", "sub-category/fall-2014-anime"),
Pair("Summer 2014", "sub-category/summer-2014-anime"),
Pair("Spring 2014", "sub-category/spring-2014-anime"),
Pair("Winter 2014", "sub-category/winter-2014-anime"),
),
)
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
AnimeFilter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second
}
} }

View File

@ -0,0 +1,410 @@
package eu.kanade.tachiyomi.animeextension.en.gogoanime
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
object GogoAnimeFilters {
open class QueryPartFilter(
displayName: String,
val vals: Array<Pair<String, String>>,
) : AnimeFilter.Select<String>(
displayName,
vals.map { it.first }.toTypedArray(),
) {
fun toQueryPart() = vals[state].second
}
open class CheckBoxFilterList(name: String, val pairs: Array<Pair<String, String>>) :
AnimeFilter.Group<AnimeFilter.CheckBox>(name, pairs.map { CheckBoxVal(it.first, false) })
private class CheckBoxVal(name: String, state: Boolean = false) : AnimeFilter.CheckBox(name, state)
private inline fun <reified R> AnimeFilterList.asQueryPart(): String {
return (getFirst<R>() as QueryPartFilter).toQueryPart()
}
private inline fun <reified R> AnimeFilterList.getFirst(): R {
return first { it is R } as R
}
private inline fun <reified R> AnimeFilterList.parseCheckbox(
options: Array<Pair<String, String>>,
name: String,
): String {
return (getFirst<R>() as CheckBoxFilterList).state
.filter { it.state }
.map { checkbox -> options.find { it.first == checkbox.name }!!.second }
.filter(String::isNotBlank)
.joinToString("&") { "$name[]=$it" }
}
class GenreSearchFilter : CheckBoxFilterList("Genre", GogoAnimeFiltersData.GENRE_SEARCH_LIST)
class CountrySearchFilter : CheckBoxFilterList("Country", GogoAnimeFiltersData.COUNTRY_SEARCH_LIST)
class SeasonSearchFilter : CheckBoxFilterList("Season", GogoAnimeFiltersData.SEASON_SEARCH_LIST)
class YearSearchFilter : CheckBoxFilterList("Year", GogoAnimeFiltersData.YEAR_SEARCH_LIST)
class LanguageSearchFilter : CheckBoxFilterList("Language", GogoAnimeFiltersData.LANGUAGE_SEARCH_LIST)
class TypeSearchFilter : CheckBoxFilterList("Type", GogoAnimeFiltersData.TYPE_SEARCH_LIST)
class StatusSearchFilter : CheckBoxFilterList("Status", GogoAnimeFiltersData.STATUS_SEARCH_LIST)
class SortSearchFilter : QueryPartFilter("Sort by", GogoAnimeFiltersData.SORT_SEARCH_LIST)
class GenreFilter : QueryPartFilter("Genre", GogoAnimeFiltersData.GENRE_LIST)
class RecentFilter : QueryPartFilter("Recent episodes", GogoAnimeFiltersData.RECENT_LIST)
class SeasonFilter : QueryPartFilter("Season", GogoAnimeFiltersData.SEASON_LIST)
val FILTER_LIST get() = AnimeFilterList(
AnimeFilter.Header("Advanced search"),
GenreSearchFilter(),
CountrySearchFilter(),
SeasonSearchFilter(),
YearSearchFilter(),
LanguageSearchFilter(),
TypeSearchFilter(),
StatusSearchFilter(),
SortSearchFilter(),
AnimeFilter.Separator(),
AnimeFilter.Header("Select sub-page"),
AnimeFilter.Header("Note: Ignores search & other filters"),
GenreFilter(),
RecentFilter(),
SeasonFilter(),
)
data class FilterSearchParams(
val filter: String = "",
val genre: String = "",
val recent: String = "",
val season: String = "",
)
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
if (filters.isEmpty()) return FilterSearchParams()
val filter = buildList {
add(filters.parseCheckbox<GenreSearchFilter>(GogoAnimeFiltersData.GENRE_SEARCH_LIST, "genre"))
add(filters.parseCheckbox<CountrySearchFilter>(GogoAnimeFiltersData.GENRE_SEARCH_LIST, "country"))
add(filters.parseCheckbox<SeasonSearchFilter>(GogoAnimeFiltersData.SEASON_SEARCH_LIST, "season"))
add(filters.parseCheckbox<YearSearchFilter>(GogoAnimeFiltersData.YEAR_SEARCH_LIST, "year"))
add(filters.parseCheckbox<LanguageSearchFilter>(GogoAnimeFiltersData.LANGUAGE_SEARCH_LIST, "language"))
add(filters.parseCheckbox<TypeSearchFilter>(GogoAnimeFiltersData.TYPE_SEARCH_LIST, "type"))
add(filters.parseCheckbox<StatusSearchFilter>(GogoAnimeFiltersData.STATUS_SEARCH_LIST, "status"))
add("sort=${filters.asQueryPart<SortSearchFilter>()}")
}.filter(String::isNotBlank).joinToString("&")
return FilterSearchParams(
filter,
filters.asQueryPart<GenreFilter>(),
filters.asQueryPart<RecentFilter>(),
filters.asQueryPart<SeasonFilter>(),
)
}
private object GogoAnimeFiltersData {
// copy($("div.cls_genre ul.dropdown-menu li").map((i,el) => `Pair("${$(el).text().trim()}", "${$(el).find("input").first().attr('value')}")`).get().join(',\n'))
// on /filter.html
val GENRE_SEARCH_LIST = arrayOf(
Pair("Action", "action"),
Pair("Adult Cast", "adult-cast"),
Pair("Adventure", "adventure"),
Pair("Anthropomorphic", "anthropomorphic"),
Pair("Avant Garde", "avant-garde"),
Pair("Boys Love", "shounen-ai"),
Pair("Cars", "cars"),
Pair("CGDCT", "cgdct"),
Pair("Childcare", "childcare"),
Pair("Comedy", "comedy"),
Pair("Comic", "comic"),
Pair("Crime", "crime"),
Pair("Crossdressing", "crossdressing"),
Pair("Delinquents", "delinquents"),
Pair("Dementia", "dementia"),
Pair("Demons", "demons"),
Pair("Detective", "detective"),
Pair("Drama", "drama"),
Pair("Dub", "dub"),
Pair("Ecchi", "ecchi"),
Pair("Erotica", "erotica"),
Pair("Family", "family"),
Pair("Fantasy", "fantasy"),
Pair("Gag Humor", "gag-humor"),
Pair("Game", "game"),
Pair("Gender Bender", "gender-bender"),
Pair("Gore", "gore"),
Pair("Gourmet", "gourmet"),
Pair("Harem", "harem"),
Pair("Hentai", "hentai"),
Pair("High Stakes Game", "high-stakes-game"),
Pair("Historical", "historical"),
Pair("Horror", "horror"),
Pair("Isekai", "isekai"),
Pair("Iyashikei", "iyashikei"),
Pair("Josei", "josei"),
Pair("Kids", "kids"),
Pair("Magic", "magic"),
Pair("Magical Sex Shift", "magical-sex-shift"),
Pair("Mahou Shoujo", "mahou-shoujo"),
Pair("Martial Arts", "martial-arts"),
Pair("Mecha", "mecha"),
Pair("Medical", "medical"),
Pair("Military", "military"),
Pair("Music", "music"),
Pair("Mystery", "mystery"),
Pair("Mythology", "mythology"),
Pair("Organized Crime", "organized-crime"),
Pair("Parody", "parody"),
Pair("Performing Arts", "performing-arts"),
Pair("Pets", "pets"),
Pair("Police", "police"),
Pair("Psychological", "psychological"),
Pair("Racing", "racing"),
Pair("Reincarnation", "reincarnation"),
Pair("Romance", "romance"),
Pair("Romantic Subtext", "romantic-subtext"),
Pair("Samurai", "samurai"),
Pair("School", "school"),
Pair("Sci-Fi", "sci-fi"),
Pair("Seinen", "seinen"),
Pair("Shoujo", "shoujo"),
Pair("Shoujo Ai", "shoujo-ai"),
Pair("Shounen", "shounen"),
Pair("Showbiz", "showbiz"),
Pair("Slice of Life", "slice-of-life"),
Pair("Space", "space"),
Pair("Sports", "sports"),
Pair("Strategy Game", "strategy-game"),
Pair("Super Power", "super-power"),
Pair("Supernatural", "supernatural"),
Pair("Survival", "survival"),
Pair("Suspense", "suspense"),
Pair("Team Sports", "team-sports"),
Pair("Thriller", "thriller"),
Pair("Time Travel", "time-travel"),
Pair("Vampire", "vampire"),
Pair("Visual Arts", "visual-arts"),
Pair("Work Life", "work-life"),
Pair("Workplace", "workplace"),
Pair("Yaoi", "yaoi"),
Pair("Yuri", "yuri"),
)
// copy($("div.cls_genre ul.dropdown-menu li").map((i,el) => `Pair("${$(el).text().trim()}", "${$(el).find("input").first().attr('value')}")`).get().join(',\n'))
// on /filter.html
val COUNTRY_SEARCH_LIST = arrayOf(
Pair("China", "5"),
Pair("Japan", "2"),
)
// copy($("div.cls_season ul.dropdown-menu li").map((i,el) => `Pair("${$(el).text().trim()}", "${$(el).find("input").first().attr('value')}")`).get().join(',\n'))
// on /filter.html
val SEASON_SEARCH_LIST = arrayOf(
Pair("Fall", "fall"),
Pair("Summer", "summer"),
Pair("Spring", "spring"),
Pair("Winter", "winter"),
)
// copy($("div.cls_year ul.dropdown-menu li").map((i,el) => `Pair("${$(el).text().trim()}", "${$(el).find("input").first().attr('value')}")`).get().join(',\n'))
// on /filter.html
val YEAR_SEARCH_LIST = arrayOf(
Pair("2023", "2023"),
Pair("2022", "2022"),
Pair("2021", "2021"),
Pair("2020", "2020"),
Pair("2019", "2019"),
Pair("2018", "2018"),
Pair("2017", "2017"),
Pair("2016", "2016"),
Pair("2015", "2015"),
Pair("2014", "2014"),
Pair("2013", "2013"),
Pair("2012", "2012"),
Pair("2011", "2011"),
Pair("2010", "2010"),
Pair("2009", "2009"),
Pair("2008", "2008"),
Pair("2007", "2007"),
Pair("2006", "2006"),
Pair("2005", "2005"),
Pair("2004", "2004"),
Pair("2003", "2003"),
Pair("2002", "2002"),
Pair("2001", "2001"),
Pair("2000", "2000"),
Pair("1999", "1999"),
)
// copy($("div.cls_lang ul.dropdown-menu li").map((i,el) => `Pair("${$(el).text().trim()}", "${$(el).find("input").first().attr('value')}")`).get().join(',\n'))
// on /filter.html
val LANGUAGE_SEARCH_LIST = arrayOf(
Pair("Sub & Dub", "subdub"),
Pair("Sub", "sub"),
Pair("Dub", "dub"),
)
// copy($("div.cls_type ul.dropdown-menu li").map((i,el) => `Pair("${$(el).text().trim()}", "${$(el).find("input").first().attr('value')}")`).get().join(',\n'))
// on /filter.html
val TYPE_SEARCH_LIST = arrayOf(
Pair("Movie", "3"),
Pair("TV", "1"),
Pair("OVA", "26"),
Pair("ONA", "30"),
Pair("Special", "2"),
Pair("Music", "32"),
)
// copy($("div.cls_status ul.dropdown-menu li").map((i,el) => `Pair("${$(el).text().trim()}", "${$(el).find("input").first().attr('value')}")`).get().join(',\n'))
// on /filter.html
val STATUS_SEARCH_LIST = arrayOf(
Pair("Not Yet Aired", "Upcoming"),
Pair("Ongoing", "Ongoing"),
Pair("Completed", "Completed"),
)
// copy($("div.cls_sort ul.dropdown-menu li").map((i,el) => `Pair("${$(el).text().trim()}", "${$(el).find("input").first().attr('value')}")`).get().join(',\n'))
// on /filter.html
val SORT_SEARCH_LIST = arrayOf(
Pair("Name A-Z", "title_az"),
Pair("Recently updated", "recently_updated"),
Pair("Recently added", "recently_added"),
Pair("Release date", "release_date"),
)
// copy($("div.dropdown-menu > a.dropdown-item").map((i,el) => `Pair("${$(el).text().trim()}", "${$(el).attr('href').trim().slice(18)}")`).get().join(',\n'))
// on /
val GENRE_LIST = arrayOf(
Pair("<select>", ""),
Pair("Action", "action"),
Pair("Adult Cast", "adult-cast"),
Pair("Adventure", "adventure"),
Pair("Anthropomorphic", "anthropomorphic"),
Pair("Avant Garde", "avant-garde"),
Pair("Boys Love", "shounen-ai"),
Pair("Cars", "cars"),
Pair("CGDCT", "cgdct"),
Pair("Childcare", "childcare"),
Pair("Comedy", "comedy"),
Pair("Comic", "comic"),
Pair("Crime", "crime"),
Pair("Crossdressing", "crossdressing"),
Pair("Delinquents", "delinquents"),
Pair("Dementia", "dementia"),
Pair("Demons", "demons"),
Pair("Detective", "detective"),
Pair("Drama", "drama"),
Pair("Dub", "dub"),
Pair("Ecchi", "ecchi"),
Pair("Erotica", "erotica"),
Pair("Family", "family"),
Pair("Fantasy", "fantasy"),
Pair("Gag Humor", "gag-humor"),
Pair("Game", "game"),
Pair("Gender Bender", "gender-bender"),
Pair("Gore", "gore"),
Pair("Gourmet", "gourmet"),
Pair("Harem", "harem"),
Pair("Hentai", "hentai"),
Pair("High Stakes Game", "high-stakes-game"),
Pair("Historical", "historical"),
Pair("Horror", "horror"),
Pair("Isekai", "isekai"),
Pair("Iyashikei", "iyashikei"),
Pair("Josei", "josei"),
Pair("Kids", "kids"),
Pair("Magic", "magic"),
Pair("Magical Sex Shift", "magical-sex-shift"),
Pair("Mahou Shoujo", "mahou-shoujo"),
Pair("Martial Arts", "martial-arts"),
Pair("Mecha", "mecha"),
Pair("Medical", "medical"),
Pair("Military", "military"),
Pair("Music", "music"),
Pair("Mystery", "mystery"),
Pair("Mythology", "mythology"),
Pair("Organized Crime", "organized-crime"),
Pair("Parody", "parody"),
Pair("Performing Arts", "performing-arts"),
Pair("Pets", "pets"),
Pair("Police", "police"),
Pair("Psychological", "psychological"),
Pair("Racing", "racing"),
Pair("Reincarnation", "reincarnation"),
Pair("Romance", "romance"),
Pair("Romantic Subtext", "romantic-subtext"),
Pair("Samurai", "samurai"),
Pair("School", "school"),
Pair("Sci-Fi", "sci-fi"),
Pair("Seinen", "seinen"),
Pair("Shoujo", "shoujo"),
Pair("Shoujo Ai", "shoujo-ai"),
Pair("Shounen", "shounen"),
Pair("Showbiz", "showbiz"),
Pair("Slice of Life", "slice-of-life"),
Pair("Space", "space"),
Pair("Sports", "sports"),
Pair("Strategy Game", "strategy-game"),
Pair("Super Power", "super-power"),
Pair("Supernatural", "supernatural"),
Pair("Survival", "survival"),
Pair("Suspense", "suspense"),
Pair("Team Sports", "team-sports"),
Pair("Thriller", "thriller"),
Pair("Time Travel", "time-travel"),
Pair("Vampire", "vampire"),
Pair("Visual Arts", "visual-arts"),
Pair("Work Life", "work-life"),
Pair("Workplace", "workplace"),
Pair("Yaoi", "yaoi"),
Pair("Yuri", "yuri"),
)
val RECENT_LIST = arrayOf(
Pair("<select>", ""),
Pair("Recent Release", "1"),
Pair("Recent Dub", "2"),
Pair("Recent Chinese", "3"),
)
val SEASON_LIST = arrayOf(
Pair("<select>", ""),
Pair("Latest season", "new-season.html"),
Pair("Summer 2023", "sub-category/summer-2023-anime"),
Pair("Spring 2023", "sub-category/spring-2023-anime"),
Pair("Winter 2023", "sub-category/winter-2023-anime"),
Pair("Fall 2022", "sub-category/fall-2022-anime"),
Pair("Summer 2022", "sub-category/summer-2022-anime"),
Pair("Spring 2022", "sub-category/spring-2022-anime"),
Pair("Winter 2022", "sub-category/winter-2022-anime"),
Pair("Fall 2021", "sub-category/fall-2021-anime"),
Pair("Summer 2021", "sub-category/summer-2021-anime"),
Pair("Spring 2021", "sub-category/spring-2021-anime"),
Pair("Winter 2021", "sub-category/winter-2021-anime"),
Pair("Fall 2020", "sub-category/fall-2020-anime"),
Pair("Summer 2020", "sub-category/summer-2020-anime"),
Pair("Spring 2020", "sub-category/spring-2020-anime"),
Pair("Winter 2020", "sub-category/winter-2020-anime"),
Pair("Fall 2019", "sub-category/fall-2019-anime"),
Pair("Summer 2019", "sub-category/summer-2019-anime"),
Pair("Spring 2019", "sub-category/spring-2019-anime"),
Pair("Winter 2019", "sub-category/winter-2019-anime"),
Pair("Fall 2018", "sub-category/fall-2018-anime"),
Pair("Summer 2018", "sub-category/summer-2018-anime"),
Pair("Spring 2018", "sub-category/spring-2018-anime"),
Pair("Winter 2018", "sub-category/winter-2018-anime"),
Pair("Fall 2017", "sub-category/fall-2017-anime"),
Pair("Summer 2017", "sub-category/summer-2017-anime"),
Pair("Spring 2017", "sub-category/spring-2017-anime"),
Pair("Winter 2017", "sub-category/winter-2017-anime"),
Pair("Fall 2016", "sub-category/fall-2016-anime"),
Pair("Summer 2016", "sub-category/summer-2016-anime"),
Pair("Spring 2016", "sub-category/spring-2016-anime"),
Pair("Winter 2016", "sub-category/winter-2016-anime"),
Pair("Fall 2015", "sub-category/fall-2015-anime"),
Pair("Summer 2015", "sub-category/summer-2015-anime"),
Pair("Spring 2015", "sub-category/spring-2015-anime"),
Pair("Winter 2015", "sub-category/winter-2015-anime"),
Pair("Fall 2014", "sub-category/fall-2014-anime"),
Pair("Summer 2014", "sub-category/summer-2014-anime"),
Pair("Spring 2014", "sub-category/spring-2014-anime"),
Pair("Winter 2014", "sub-category/winter-2014-anime"),
)
}
}

View File

@ -0,0 +1,24 @@
package eu.kanade.tachiyomi.animeextension.en.gogoanime.extractors
import dev.datlag.jsunpacker.JsUnpacker
import eu.kanade.tachiyomi.animesource.model.Video
import eu.kanade.tachiyomi.lib.playlistutils.PlaylistUtils
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Headers
import okhttp3.OkHttpClient
class StreamWishExtractor(private val client: OkHttpClient, private val headers: Headers) {
fun videosFromUrl(url: String, videoNameGen: (String) -> String = { quality -> "StreamWish - $quality" }): List<Video> {
val doc = client.newCall(GET(url, headers = headers)).execute().asJsoup()
val jsEval = doc.selectFirst("script:containsData(m3u8)")?.data() ?: return emptyList()
val masterUrl = JsUnpacker.unpackAndCombine(jsEval)
?.substringAfter("source")
?.substringAfter("file:\"")
?.substringBefore("\"")
?: return emptyList()
return PlaylistUtils(client, headers).extractFromHls(masterUrl, videoNameGen = videoNameGen)
}
}