Fix(en/AniPlay): Update source (#206)
This commit is contained in:
parent
7a71a9d6fd
commit
ffb39ec544
8 changed files with 413 additions and 55 deletions
|
@ -2,4 +2,4 @@ plugins {
|
||||||
id("lib-multisrc")
|
id("lib-multisrc")
|
||||||
}
|
}
|
||||||
|
|
||||||
baseVersionCode = 2
|
baseVersionCode = 3
|
||||||
|
|
|
@ -8,6 +8,10 @@ import eu.kanade.tachiyomi.network.POST
|
||||||
import eu.kanade.tachiyomi.util.parseAs
|
import eu.kanade.tachiyomi.util.parseAs
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.add
|
||||||
|
import kotlinx.serialization.json.buildJsonObject
|
||||||
|
import kotlinx.serialization.json.put
|
||||||
|
import kotlinx.serialization.json.putJsonArray
|
||||||
import okhttp3.FormBody
|
import okhttp3.FormBody
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
|
@ -32,7 +36,7 @@ abstract class AniListAnimeHttpSource : AnimeHttpSource() {
|
||||||
query = ANIME_LIST_QUERY,
|
query = ANIME_LIST_QUERY,
|
||||||
variables = AnimeListVariables(
|
variables = AnimeListVariables(
|
||||||
page = page,
|
page = page,
|
||||||
sort = AnimeListVariables.MediaSort.POPULARITY_DESC,
|
sort = AnimeListVariables.MediaSort.TRENDING_DESC,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -58,20 +62,58 @@ abstract class AniListAnimeHttpSource : AnimeHttpSource() {
|
||||||
|
|
||||||
/* ===================================== Search Anime ===================================== */
|
/* ===================================== Search Anime ===================================== */
|
||||||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
|
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
|
||||||
return buildAnimeListRequest(
|
val params = AniListFilters.getSearchParameters(filters)
|
||||||
query = ANIME_LIST_QUERY,
|
|
||||||
variables = AnimeListVariables(
|
val variablesObject = buildJsonObject {
|
||||||
page = page,
|
put("page", page)
|
||||||
sort = AnimeListVariables.MediaSort.SEARCH_MATCH,
|
put("perPage", 30)
|
||||||
search = query.ifBlank { null },
|
put("isAdult", false)
|
||||||
),
|
put("type", "ANIME")
|
||||||
)
|
put("sort", params.sort)
|
||||||
|
if (query.isNotBlank()) put("search", query)
|
||||||
|
|
||||||
|
if (params.genres.isNotEmpty()) {
|
||||||
|
putJsonArray("genres") {
|
||||||
|
params.genres.forEach { add(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.format.isNotEmpty()) {
|
||||||
|
putJsonArray("format") {
|
||||||
|
params.format.forEach { add(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.season.isBlank() && params.year.isNotBlank()) {
|
||||||
|
put("year", "${params.year}%")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.season.isNotBlank() && params.year.isBlank()) {
|
||||||
|
throw Exception("Year cannot be blank if season is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.season.isNotBlank() && params.year.isNotBlank()) {
|
||||||
|
put("season", params.season)
|
||||||
|
put("seasonYear", params.year)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.status.isNotBlank()) {
|
||||||
|
put("status", params.status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val variables = json.encodeToString(variablesObject)
|
||||||
|
|
||||||
|
return buildRequest(query = SORT_QUERY, variables = variables)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun searchAnimeParse(response: Response): AnimesPage {
|
override fun searchAnimeParse(response: Response): AnimesPage {
|
||||||
return parseAnimeListResponse(response)
|
return parseAnimeListResponse(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================== Filters ===============================
|
||||||
|
|
||||||
|
override fun getFilterList(): AnimeFilterList = AniListFilters.FILTER_LIST
|
||||||
|
|
||||||
/* ===================================== Anime Details ===================================== */
|
/* ===================================== Anime Details ===================================== */
|
||||||
override fun animeDetailsRequest(anime: SAnime): Request {
|
override fun animeDetailsRequest(anime: SAnime): Request {
|
||||||
return buildRequest(
|
return buildRequest(
|
||||||
|
@ -145,6 +187,9 @@ abstract class AniListAnimeHttpSource : AnimeHttpSource() {
|
||||||
status = when (media.status) {
|
status = when (media.status) {
|
||||||
AniListMedia.Status.RELEASING -> SAnime.ONGOING
|
AniListMedia.Status.RELEASING -> SAnime.ONGOING
|
||||||
AniListMedia.Status.FINISHED -> SAnime.COMPLETED
|
AniListMedia.Status.FINISHED -> SAnime.COMPLETED
|
||||||
|
AniListMedia.Status.NOT_YET_RELEASED -> SAnime.LICENSED
|
||||||
|
AniListMedia.Status.CANCELLED -> SAnime.CANCELLED
|
||||||
|
AniListMedia.Status.HIATUS -> SAnime.ON_HIATUS
|
||||||
}
|
}
|
||||||
thumbnail_url = media.coverImage.large
|
thumbnail_url = media.coverImage.large
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,236 @@
|
||||||
|
package eu.kanade.tachiyomi.multisrc.anilist
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
|
||||||
|
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||||
|
|
||||||
|
object AniListFilters {
|
||||||
|
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.parseCheckboxList(
|
||||||
|
options: Array<Pair<String, String>>,
|
||||||
|
): List<String> {
|
||||||
|
return (getFirst<R>() as CheckBoxFilterList).state
|
||||||
|
.filter { it.state }
|
||||||
|
.map { checkBox -> options.find { it.first == checkBox.name }!!.second }
|
||||||
|
.filter(String::isNotBlank)
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <reified R> AnimeFilterList.getSort(): String {
|
||||||
|
val state = (getFirst<R>() as AnimeFilter.Sort).state ?: return ""
|
||||||
|
val index = state.index
|
||||||
|
val suffix = if (state.ascending) "" else "_DESC"
|
||||||
|
return AniListFiltersData.SORT_LIST[index].second + suffix
|
||||||
|
}
|
||||||
|
|
||||||
|
class GenreFilter : CheckBoxFilterList("Genres", AniListFiltersData.GENRE_LIST)
|
||||||
|
class YearFilter : QueryPartFilter("Year", AniListFiltersData.YEAR_LIST)
|
||||||
|
class SeasonFilter : QueryPartFilter("Season", AniListFiltersData.SEASON_LIST)
|
||||||
|
class FormatFilter : CheckBoxFilterList("Format", AniListFiltersData.FORMAT_LIST)
|
||||||
|
class StatusFilter : QueryPartFilter("Airing Status", AniListFiltersData.STATUS_LIST)
|
||||||
|
|
||||||
|
class SortFilter : AnimeFilter.Sort(
|
||||||
|
"Sort",
|
||||||
|
AniListFiltersData.SORT_LIST.map { it.first }.toTypedArray(),
|
||||||
|
Selection(1, false),
|
||||||
|
)
|
||||||
|
|
||||||
|
val FILTER_LIST get() = AnimeFilterList(
|
||||||
|
GenreFilter(),
|
||||||
|
YearFilter(),
|
||||||
|
SeasonFilter(),
|
||||||
|
FormatFilter(),
|
||||||
|
StatusFilter(),
|
||||||
|
SortFilter(),
|
||||||
|
)
|
||||||
|
|
||||||
|
class FilterSearchParams(
|
||||||
|
val genres: List<String> = emptyList(),
|
||||||
|
val year: String = "",
|
||||||
|
val season: String = "",
|
||||||
|
val format: List<String> = emptyList(),
|
||||||
|
val status: String = "",
|
||||||
|
val sort: String = "",
|
||||||
|
)
|
||||||
|
|
||||||
|
internal fun getSearchParameters(filters: AnimeFilterList): FilterSearchParams {
|
||||||
|
if (filters.isEmpty()) return FilterSearchParams()
|
||||||
|
|
||||||
|
return FilterSearchParams(
|
||||||
|
filters.parseCheckboxList<GenreFilter>(AniListFiltersData.GENRE_LIST),
|
||||||
|
filters.asQueryPart<YearFilter>(),
|
||||||
|
filters.asQueryPart<SeasonFilter>(),
|
||||||
|
filters.parseCheckboxList<FormatFilter>(AniListFiltersData.FORMAT_LIST),
|
||||||
|
filters.asQueryPart<StatusFilter>(),
|
||||||
|
filters.getSort<SortFilter>(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private object AniListFiltersData {
|
||||||
|
val GENRE_LIST = arrayOf(
|
||||||
|
Pair("Action", "Action"),
|
||||||
|
Pair("Adventure", "Adventure"),
|
||||||
|
Pair("Comedy", "Comedy"),
|
||||||
|
Pair("Drama", "Drama"),
|
||||||
|
Pair("Ecchi", "Ecchi"),
|
||||||
|
Pair("Fantasy", "Fantasy"),
|
||||||
|
Pair("Horror", "Horror"),
|
||||||
|
Pair("Mahou Shoujo", "Mahou Shoujo"),
|
||||||
|
Pair("Mecha", "Mecha"),
|
||||||
|
Pair("Music", "Music"),
|
||||||
|
Pair("Mystery", "Mystery"),
|
||||||
|
Pair("Psychological", "Psychological"),
|
||||||
|
Pair("Romance", "Romance"),
|
||||||
|
Pair("Sci-Fi", "Sci-Fi"),
|
||||||
|
Pair("Slice of Life", "Slice of Life"),
|
||||||
|
Pair("Sports", "Sports"),
|
||||||
|
Pair("Supernatural", "Supernatural"),
|
||||||
|
Pair("Thriller", "Thriller"),
|
||||||
|
)
|
||||||
|
|
||||||
|
val YEAR_LIST = arrayOf(
|
||||||
|
Pair("<Select>", ""),
|
||||||
|
Pair("2024", "2024"),
|
||||||
|
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"),
|
||||||
|
Pair("1998", "1998"),
|
||||||
|
Pair("1997", "1997"),
|
||||||
|
Pair("1996", "1996"),
|
||||||
|
Pair("1995", "1995"),
|
||||||
|
Pair("1994", "1994"),
|
||||||
|
Pair("1993", "1993"),
|
||||||
|
Pair("1992", "1992"),
|
||||||
|
Pair("1991", "1991"),
|
||||||
|
Pair("1990", "1990"),
|
||||||
|
Pair("1989", "1989"),
|
||||||
|
Pair("1988", "1988"),
|
||||||
|
Pair("1987", "1987"),
|
||||||
|
Pair("1986", "1986"),
|
||||||
|
Pair("1985", "1985"),
|
||||||
|
Pair("1984", "1984"),
|
||||||
|
Pair("1983", "1983"),
|
||||||
|
Pair("1982", "1982"),
|
||||||
|
Pair("1981", "1981"),
|
||||||
|
Pair("1980", "1980"),
|
||||||
|
Pair("1979", "1979"),
|
||||||
|
Pair("1978", "1978"),
|
||||||
|
Pair("1977", "1977"),
|
||||||
|
Pair("1976", "1976"),
|
||||||
|
Pair("1975", "1975"),
|
||||||
|
Pair("1974", "1974"),
|
||||||
|
Pair("1973", "1973"),
|
||||||
|
Pair("1972", "1972"),
|
||||||
|
Pair("1971", "1971"),
|
||||||
|
Pair("1970", "1970"),
|
||||||
|
Pair("1969", "1969"),
|
||||||
|
Pair("1968", "1968"),
|
||||||
|
Pair("1967", "1967"),
|
||||||
|
Pair("1966", "1966"),
|
||||||
|
Pair("1965", "1965"),
|
||||||
|
Pair("1964", "1964"),
|
||||||
|
Pair("1963", "1963"),
|
||||||
|
Pair("1962", "1962"),
|
||||||
|
Pair("1961", "1961"),
|
||||||
|
Pair("1960", "1960"),
|
||||||
|
Pair("1959", "1959"),
|
||||||
|
Pair("1958", "1958"),
|
||||||
|
Pair("1957", "1957"),
|
||||||
|
Pair("1956", "1956"),
|
||||||
|
Pair("1955", "1955"),
|
||||||
|
Pair("1954", "1954"),
|
||||||
|
Pair("1953", "1953"),
|
||||||
|
Pair("1952", "1952"),
|
||||||
|
Pair("1951", "1951"),
|
||||||
|
Pair("1950", "1950"),
|
||||||
|
Pair("1949", "1949"),
|
||||||
|
Pair("1948", "1948"),
|
||||||
|
Pair("1947", "1947"),
|
||||||
|
Pair("1946", "1946"),
|
||||||
|
Pair("1945", "1945"),
|
||||||
|
Pair("1944", "1944"),
|
||||||
|
Pair("1943", "1943"),
|
||||||
|
Pair("1942", "1942"),
|
||||||
|
Pair("1941", "1941"),
|
||||||
|
Pair("1940", "1940"),
|
||||||
|
)
|
||||||
|
|
||||||
|
val SEASON_LIST = arrayOf(
|
||||||
|
Pair("<Select>", ""),
|
||||||
|
Pair("Winter", "WINTER"),
|
||||||
|
Pair("Spring", "SPRING"),
|
||||||
|
Pair("Summer", "SUMMER"),
|
||||||
|
Pair("Fall", "FALL"),
|
||||||
|
)
|
||||||
|
|
||||||
|
val FORMAT_LIST = arrayOf(
|
||||||
|
Pair("TV Show", "TV"),
|
||||||
|
Pair("Movie", "MOVIE"),
|
||||||
|
Pair("TV Short", "TV_SHORT"),
|
||||||
|
Pair("Special", "SPECIAL"),
|
||||||
|
Pair("OVA", "OVA"),
|
||||||
|
Pair("ONA", "ONA"),
|
||||||
|
Pair("Music", "MUSIC"),
|
||||||
|
)
|
||||||
|
|
||||||
|
val STATUS_LIST = arrayOf(
|
||||||
|
Pair("<Select>", ""),
|
||||||
|
Pair("Airing", "RELEASING"),
|
||||||
|
Pair("Finished", "FINISHED"),
|
||||||
|
Pair("Not Yet Aired", "NOT_YET_RELEASED"),
|
||||||
|
Pair("Cancelled", "CANCELLED"),
|
||||||
|
)
|
||||||
|
|
||||||
|
val SORT_LIST = arrayOf(
|
||||||
|
Pair("Title", "TITLE_ENGLISH"),
|
||||||
|
Pair("Popularity", "POPULARITY"),
|
||||||
|
Pair("Average Score", "SCORE"),
|
||||||
|
Pair("Trending", "TRENDING"),
|
||||||
|
Pair("Favorites", "FAVOURITES"),
|
||||||
|
Pair("Date Added", "ID"),
|
||||||
|
Pair("Release Date", "START_DATE"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -55,16 +55,76 @@ internal const val ANIME_DETAILS_QUERY = """
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
private fun String.toQuery() = this.trimIndent().replace("%", "$")
|
||||||
|
internal val SORT_QUERY = """
|
||||||
|
query (
|
||||||
|
${"$"}page: Int,
|
||||||
|
${"$"}perPage: Int,
|
||||||
|
${"$"}isAdult: Boolean,
|
||||||
|
${"$"}type: MediaType,
|
||||||
|
${"$"}sort: [MediaSort],
|
||||||
|
${"$"}status: MediaStatus,
|
||||||
|
${"$"}search: String,
|
||||||
|
${"$"}genres: [String],
|
||||||
|
${"$"}year: String,
|
||||||
|
${"$"}seasonYear: Int,
|
||||||
|
${"$"}season: MediaSeason,
|
||||||
|
${"$"}format: [MediaFormat]
|
||||||
|
) {
|
||||||
|
Page (page: ${"$"}page, perPage: ${"$"}perPage) {
|
||||||
|
pageInfo {
|
||||||
|
hasNextPage
|
||||||
|
}
|
||||||
|
media (
|
||||||
|
isAdult: ${"$"}isAdult,
|
||||||
|
type: ${"$"}type,
|
||||||
|
sort: ${"$"}sort,
|
||||||
|
status: ${"$"}status,
|
||||||
|
search: ${"$"}search,
|
||||||
|
genre_in: ${"$"}genres,
|
||||||
|
startDate_like: ${"$"}year,
|
||||||
|
seasonYear: ${"$"}seasonYear,
|
||||||
|
season: ${"$"}season,
|
||||||
|
format_in: ${"$"}format
|
||||||
|
) {
|
||||||
|
id
|
||||||
|
title {
|
||||||
|
romaji
|
||||||
|
english
|
||||||
|
native
|
||||||
|
}
|
||||||
|
coverImage {
|
||||||
|
extraLarge
|
||||||
|
large
|
||||||
|
medium
|
||||||
|
}
|
||||||
|
status
|
||||||
|
genres
|
||||||
|
studios {
|
||||||
|
nodes {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".toQuery()
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal data class AnimeListVariables(
|
internal data class AnimeListVariables(
|
||||||
val page: Int,
|
val page: Int,
|
||||||
val sort: MediaSort,
|
val sort: MediaSort,
|
||||||
val search: String? = null,
|
val search: String? = null,
|
||||||
|
val genre: String? = null,
|
||||||
|
val year: String? = null,
|
||||||
|
val status: String? = null,
|
||||||
|
val format: String? = null,
|
||||||
|
val season: String? = null,
|
||||||
|
val seasonYear: String? = null,
|
||||||
|
val isAdult: Boolean = false,
|
||||||
) {
|
) {
|
||||||
enum class MediaSort {
|
enum class MediaSort {
|
||||||
POPULARITY_DESC,
|
TRENDING_DESC,
|
||||||
SEARCH_MATCH,
|
|
||||||
START_DATE_DESC,
|
START_DATE_DESC,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,9 @@ internal data class AniListMedia(
|
||||||
enum class Status {
|
enum class Status {
|
||||||
RELEASING,
|
RELEASING,
|
||||||
FINISHED,
|
FINISHED,
|
||||||
|
NOT_YET_RELEASED,
|
||||||
|
CANCELLED,
|
||||||
|
HIATUS,
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|
|
@ -2,7 +2,7 @@ ext {
|
||||||
extName = 'AniPlay'
|
extName = 'AniPlay'
|
||||||
extClass = '.AniPlay'
|
extClass = '.AniPlay'
|
||||||
themePkg = 'anilist'
|
themePkg = 'anilist'
|
||||||
overrideVersionCode = 1
|
overrideVersionCode = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
|
|
@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.animeextension.en.aniplay
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.preference.ListPreference
|
import androidx.preference.ListPreference
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
|
@ -17,6 +18,7 @@ import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.network.POST
|
import eu.kanade.tachiyomi.network.POST
|
||||||
import eu.kanade.tachiyomi.util.parallelFlatMapBlocking
|
import eu.kanade.tachiyomi.util.parallelFlatMapBlocking
|
||||||
import eu.kanade.tachiyomi.util.parseAs
|
import eu.kanade.tachiyomi.util.parseAs
|
||||||
|
import kotlinx.serialization.SerializationException
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
|
@ -25,6 +27,7 @@ import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.io.IOException
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
|
@ -83,26 +86,26 @@ class AniPlay : AniListAnimeHttpSource(), ConfigurableAnimeSource {
|
||||||
|
|
||||||
providers.forEach { provider ->
|
providers.forEach { provider ->
|
||||||
provider.episodes.forEach { episode ->
|
provider.episodes.forEach { episode ->
|
||||||
if (!episodes.containsKey(episode.number)) {
|
val episodeNumber = episode.number.toString().toIntOrNull() ?: episode.number.toInt()
|
||||||
episodes[episode.number] = episode
|
if (!episodes.containsKey(episodeNumber)) {
|
||||||
|
episodes[episodeNumber] = episode
|
||||||
}
|
}
|
||||||
val existingEpisodeExtras = episodeExtras.getOrElse(episode.number) { emptyList() }
|
val existingEpisodeExtras = episodeExtras.getOrElse(episodeNumber) { emptyList() }
|
||||||
val episodeExtra = EpisodeExtra(
|
val episodeExtra = EpisodeExtra(
|
||||||
source = provider.providerId,
|
source = provider.providerId,
|
||||||
episodeId = episode.id,
|
episodeId = episode.id,
|
||||||
hasDub = episode.hasDub,
|
hasDub = episode.hasDub,
|
||||||
)
|
)
|
||||||
episodeExtras[episode.number] = existingEpisodeExtras + listOf(episodeExtra)
|
episodeExtras[episodeNumber] = existingEpisodeExtras + listOf(episodeExtra)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return episodes.map { episodeMap ->
|
return episodes.map { episodeMap ->
|
||||||
val episode = episodeMap.value
|
val episode = episodeMap.value
|
||||||
val episodeNumber = episode.number
|
val episodeNumber = episodeMap.key
|
||||||
val episodeExtra = episodeExtras.getValue(episodeNumber)
|
val episodeExtra = episodeExtras.getValue(episodeNumber)
|
||||||
val episodeExtraString = json.encodeToString(episodeExtra)
|
val episodeExtraString = json.encodeToString(episodeExtra)
|
||||||
.let { Base64.encode(it.toByteArray(), Base64.DEFAULT) }
|
.let { Base64.encodeToString(it.toByteArray(), Base64.DEFAULT) }
|
||||||
.toString(Charsets.UTF_8)
|
|
||||||
|
|
||||||
val url = baseUrl.toHttpUrl().newBuilder()
|
val url = baseUrl.toHttpUrl().newBuilder()
|
||||||
.addPathSegment("anime")
|
.addPathSegment("anime")
|
||||||
|
@ -112,7 +115,7 @@ class AniPlay : AniListAnimeHttpSource(), ConfigurableAnimeSource {
|
||||||
.addQueryParameter("extras", episodeExtraString)
|
.addQueryParameter("extras", episodeExtraString)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val name = parseEpisodeName(episodeNumber, episode.title)
|
val name = parseEpisodeName(episodeNumber.toString(), episode.title)
|
||||||
val uploadDate = parseDate(episode.createdAt)
|
val uploadDate = parseDate(episode.createdAt)
|
||||||
val dub = when {
|
val dub = when {
|
||||||
episodeExtra.any { it.hasDub } -> ", Dub"
|
episodeExtra.any { it.hasDub } -> ", Dub"
|
||||||
|
@ -142,45 +145,57 @@ class AniPlay : AniListAnimeHttpSource(), ConfigurableAnimeSource {
|
||||||
val episodeNum = episodeUrl.queryParameter("ep") ?: return emptyList()
|
val episodeNum = episodeUrl.queryParameter("ep") ?: return emptyList()
|
||||||
val extras = episodeUrl.queryParameter("extras")
|
val extras = episodeUrl.queryParameter("extras")
|
||||||
?.let {
|
?.let {
|
||||||
|
try {
|
||||||
Base64.decode(it, Base64.DEFAULT).toString(Charsets.UTF_8)
|
Base64.decode(it, Base64.DEFAULT).toString(Charsets.UTF_8)
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
Log.e("AniPlay", "Error decoding base64", e)
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?.let {
|
||||||
|
try {
|
||||||
|
json.decodeFromString<List<EpisodeExtra>>(it)
|
||||||
|
} catch (e: SerializationException) {
|
||||||
|
Log.e("AniPlay", "Error parsing JSON", e)
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
?.let { json.decodeFromString<List<EpisodeExtra>>(it) }
|
|
||||||
?: emptyList()
|
?: emptyList()
|
||||||
|
|
||||||
val episodeDataList = extras.parallelFlatMapBlocking { extra ->
|
val episodeDataList = extras.parallelFlatMapBlocking { extra ->
|
||||||
val languages = mutableListOf("sub")
|
val languages = mutableListOf("sub").apply {
|
||||||
if (extra.hasDub) {
|
if (extra.hasDub) add("dub")
|
||||||
languages.add("dub")
|
|
||||||
}
|
}
|
||||||
val url = "$baseUrl/api/anime/source/$animeId"
|
val url = "$baseUrl/api/anime/source/$animeId"
|
||||||
|
|
||||||
languages.map { language ->
|
languages.map { language ->
|
||||||
val requestBody = json
|
val requestBody = json.encodeToString(
|
||||||
.encodeToString(
|
|
||||||
VideoSourceRequest(
|
VideoSourceRequest(
|
||||||
source = extra.source,
|
source = extra.source,
|
||||||
episodeId = extra.episodeId,
|
episodeId = extra.episodeId,
|
||||||
episodeNum = episodeNum,
|
episodeNum = episodeNum,
|
||||||
subType = language,
|
subType = language,
|
||||||
),
|
),
|
||||||
)
|
).toRequestBody("application/json".toMediaType())
|
||||||
.toRequestBody("application/json".toMediaType())
|
|
||||||
|
|
||||||
val response = client
|
try {
|
||||||
.newCall(POST(url = url, body = requestBody))
|
val response = client.newCall(POST(url = url, body = requestBody)).execute().parseAs<VideoSourceResponse>()
|
||||||
.execute()
|
|
||||||
.parseAs<VideoSourceResponse>()
|
|
||||||
|
|
||||||
EpisodeData(
|
EpisodeData(
|
||||||
source = extra.source,
|
source = extra.source,
|
||||||
language = language,
|
language = language,
|
||||||
response = response,
|
response = response,
|
||||||
)
|
)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
null // Return null to be filtered out
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null // Return null to be filtered out
|
||||||
}
|
}
|
||||||
|
}.filterNotNull() // Filter out null values due to errors
|
||||||
}
|
}
|
||||||
|
|
||||||
val videos = episodeDataList.flatMap { episodeData ->
|
val videos = episodeDataList.flatMap { episodeData ->
|
||||||
val defaultSource = episodeData.response.sources?.first {
|
val defaultSource = episodeData.response.sources?.firstOrNull {
|
||||||
it.quality in listOf("default", "auto")
|
it.quality in listOf("default", "auto")
|
||||||
} ?: return@flatMap emptyList()
|
} ?: return@flatMap emptyList()
|
||||||
|
|
||||||
|
@ -316,14 +331,13 @@ class AniPlay : AniListAnimeHttpSource(), ConfigurableAnimeSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* =================================== AniPlay Utilities =================================== */
|
/* =================================== AniPlay Utilities =================================== */
|
||||||
|
private fun parseEpisodeName(number: String, title: String?): String {
|
||||||
private fun parseEpisodeName(number: Int, name: String): String {
|
return if (title.isNullOrBlank()) {
|
||||||
return when {
|
"Episode $number"
|
||||||
listOf("EP ", "EPISODE ").any(name::startsWith) -> "Episode $number"
|
} else {
|
||||||
else -> "Episode $number: $name"
|
"Episode $number: $title"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getServerName(value: String): String {
|
private fun getServerName(value: String): String {
|
||||||
val index = PREF_SERVER_ENTRY_VALUES.indexOf(value)
|
val index = PREF_SERVER_ENTRY_VALUES.indexOf(value)
|
||||||
return PREF_SERVER_ENTRIES[index]
|
return PREF_SERVER_ENTRIES[index]
|
||||||
|
|
|
@ -12,7 +12,7 @@ data class EpisodeListResponse(
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Episode(
|
data class Episode(
|
||||||
val id: String,
|
val id: String,
|
||||||
val number: Int,
|
val number: Float,
|
||||||
val title: String,
|
val title: String,
|
||||||
val hasDub: Boolean,
|
val hasDub: Boolean,
|
||||||
val isFiller: Boolean,
|
val isFiller: Boolean,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue