fix(all/sudatchi): Replace api calls with NEXT json (#3326)
This commit is contained in:
parent
ee69716a7a
commit
356ce6f2a9
@ -1,7 +1,8 @@
|
|||||||
ext {
|
ext {
|
||||||
extName = 'Sudatchi'
|
extName = 'Sudatchi'
|
||||||
extClass = '.Sudatchi'
|
extClass = '.Sudatchi'
|
||||||
extVersionCode = 2
|
extVersionCode = 3
|
||||||
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
@ -4,12 +4,13 @@ import android.app.Application
|
|||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import androidx.preference.ListPreference
|
import androidx.preference.ListPreference
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
|
import eu.kanade.tachiyomi.animeextension.all.sudatchi.dto.AnimeDto
|
||||||
|
import eu.kanade.tachiyomi.animeextension.all.sudatchi.dto.AnimePageDto
|
||||||
import eu.kanade.tachiyomi.animeextension.all.sudatchi.dto.DirectoryDto
|
import eu.kanade.tachiyomi.animeextension.all.sudatchi.dto.DirectoryDto
|
||||||
import eu.kanade.tachiyomi.animeextension.all.sudatchi.dto.HomeListDto
|
import eu.kanade.tachiyomi.animeextension.all.sudatchi.dto.EpisodePageDto
|
||||||
import eu.kanade.tachiyomi.animeextension.all.sudatchi.dto.LongAnimeDto
|
import eu.kanade.tachiyomi.animeextension.all.sudatchi.dto.HomePageDto
|
||||||
import eu.kanade.tachiyomi.animeextension.all.sudatchi.dto.ShortAnimeDto
|
import eu.kanade.tachiyomi.animeextension.all.sudatchi.dto.PropsDto
|
||||||
import eu.kanade.tachiyomi.animeextension.all.sudatchi.dto.SubtitleDto
|
import eu.kanade.tachiyomi.animeextension.all.sudatchi.dto.SubtitleDto
|
||||||
import eu.kanade.tachiyomi.animeextension.all.sudatchi.dto.WatchDto
|
|
||||||
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
|
||||||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
|
||||||
import eu.kanade.tachiyomi.animesource.model.AnimesPage
|
import eu.kanade.tachiyomi.animesource.model.AnimesPage
|
||||||
@ -27,6 +28,7 @@ import kotlinx.serialization.json.Json
|
|||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
|
import org.jsoup.nodes.Document
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
@ -37,6 +39,8 @@ class Sudatchi : AnimeHttpSource(), ConfigurableAnimeSource {
|
|||||||
|
|
||||||
override val baseUrl = "https://sudatchi.com"
|
override val baseUrl = "https://sudatchi.com"
|
||||||
|
|
||||||
|
private val ipfsUrl = "https://ipfs.animeui.com"
|
||||||
|
|
||||||
override val lang = "all"
|
override val lang = "all"
|
||||||
|
|
||||||
override val supportsLatest = true
|
override val supportsLatest = true
|
||||||
@ -52,7 +56,7 @@ class Sudatchi : AnimeHttpSource(), ConfigurableAnimeSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ============================== Popular ===============================
|
// ============================== Popular ===============================
|
||||||
override fun popularAnimeRequest(page: Int) = GET("$baseUrl/api/home-list", headers)
|
override fun popularAnimeRequest(page: Int) = GET(baseUrl, headers)
|
||||||
|
|
||||||
private fun Int.parseStatus() = when (this) {
|
private fun Int.parseStatus() = when (this) {
|
||||||
1 -> SAnime.UNKNOWN // Not Yet Released
|
1 -> SAnime.UNKNOWN // Not Yet Released
|
||||||
@ -61,7 +65,7 @@ class Sudatchi : AnimeHttpSource(), ConfigurableAnimeSource {
|
|||||||
else -> SAnime.UNKNOWN
|
else -> SAnime.UNKNOWN
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ShortAnimeDto.toSAnime(titleLang: String) = SAnime.create().apply {
|
private fun AnimeDto.toSAnime(titleLang: String) = SAnime.create().apply {
|
||||||
url = "/anime/$slug"
|
url = "/anime/$slug"
|
||||||
title = when (titleLang) {
|
title = when (titleLang) {
|
||||||
"romaji" -> titleRomanji
|
"romaji" -> titleRomanji
|
||||||
@ -70,14 +74,19 @@ class Sudatchi : AnimeHttpSource(), ConfigurableAnimeSource {
|
|||||||
} ?: arrayOf(titleEnglish, titleRomanji, titleJapanese, "").firstNotNullOf { it }
|
} ?: arrayOf(titleEnglish, titleRomanji, titleJapanese, "").firstNotNullOf { it }
|
||||||
description = synopsis
|
description = synopsis
|
||||||
status = statusId.parseStatus()
|
status = statusId.parseStatus()
|
||||||
thumbnail_url = "$baseUrl$imgUrl"
|
thumbnail_url = when {
|
||||||
|
imgUrl.startsWith("/") -> "$baseUrl$imgUrl"
|
||||||
|
else -> "$ipfsUrl/ipfs/$imgUrl"
|
||||||
|
}
|
||||||
genre = animeGenres?.joinToString { it.genre.name }
|
genre = animeGenres?.joinToString { it.genre.name }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun popularAnimeParse(response: Response): AnimesPage {
|
override fun popularAnimeParse(response: Response): AnimesPage {
|
||||||
sudatchiFilters.fetchFilters()
|
sudatchiFilters.fetchFilters()
|
||||||
val titleLang = preferences.title
|
val titleLang = preferences.title
|
||||||
return AnimesPage(response.parseAs<HomeListDto>().animeSpotlight.map { it.toSAnime(titleLang) }, false)
|
val document = response.asJsoup()
|
||||||
|
val data = document.parseAs<HomePageDto>().animeSpotlight
|
||||||
|
return AnimesPage(data.map { it.toSAnime(titleLang) }, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================== Latest ===============================
|
// =============================== Latest ===============================
|
||||||
@ -97,7 +106,7 @@ class Sudatchi : AnimeHttpSource(), ConfigurableAnimeSource {
|
|||||||
override suspend fun getSearchAnime(page: Int, query: String, filters: AnimeFilterList): AnimesPage {
|
override suspend fun getSearchAnime(page: Int, query: String, filters: AnimeFilterList): AnimesPage {
|
||||||
return if (query.startsWith(PREFIX_SEARCH)) { // URL intent handler
|
return if (query.startsWith(PREFIX_SEARCH)) { // URL intent handler
|
||||||
val id = query.removePrefix(PREFIX_SEARCH)
|
val id = query.removePrefix(PREFIX_SEARCH)
|
||||||
client.newCall(GET("$baseUrl/api/anime/$id", headers))
|
client.newCall(GET("$baseUrl/anime/$id", headers))
|
||||||
.awaitSuccess()
|
.awaitSuccess()
|
||||||
.use(::searchAnimeByIdParse)
|
.use(::searchAnimeByIdParse)
|
||||||
} else {
|
} else {
|
||||||
@ -130,15 +139,18 @@ class Sudatchi : AnimeHttpSource(), ConfigurableAnimeSource {
|
|||||||
// =========================== Anime Details ============================
|
// =========================== Anime Details ============================
|
||||||
override fun getAnimeUrl(anime: SAnime) = "$baseUrl${anime.url}"
|
override fun getAnimeUrl(anime: SAnime) = "$baseUrl${anime.url}"
|
||||||
|
|
||||||
override fun animeDetailsRequest(anime: SAnime) = GET("$baseUrl/api${anime.url}", headers)
|
override fun animeDetailsParse(response: Response): SAnime {
|
||||||
|
val document = response.asJsoup()
|
||||||
override fun animeDetailsParse(response: Response) = response.parseAs<ShortAnimeDto>().toSAnime(preferences.title)
|
val data = document.parseAs<AnimePageDto>().animeData
|
||||||
|
return data.toSAnime(preferences.title)
|
||||||
|
}
|
||||||
|
|
||||||
// ============================== Episodes ==============================
|
// ============================== Episodes ==============================
|
||||||
override fun episodeListRequest(anime: SAnime) = animeDetailsRequest(anime)
|
override fun episodeListRequest(anime: SAnime) = animeDetailsRequest(anime)
|
||||||
|
|
||||||
override fun episodeListParse(response: Response): List<SEpisode> {
|
override fun episodeListParse(response: Response): List<SEpisode> {
|
||||||
val anime = response.parseAs<LongAnimeDto>()
|
val document = response.asJsoup()
|
||||||
|
val anime = document.parseAs<AnimePageDto>().animeData
|
||||||
return anime.episodes.map {
|
return anime.episodes.map {
|
||||||
SEpisode.create().apply {
|
SEpisode.create().apply {
|
||||||
name = it.title
|
name = it.title
|
||||||
@ -155,8 +167,7 @@ class Sudatchi : AnimeHttpSource(), ConfigurableAnimeSource {
|
|||||||
|
|
||||||
override fun videoListParse(response: Response): List<Video> {
|
override fun videoListParse(response: Response): List<Video> {
|
||||||
val document = response.asJsoup()
|
val document = response.asJsoup()
|
||||||
val jsonString = document.selectFirst("script#__NEXT_DATA__")?.data() ?: return emptyList()
|
val data = document.parseAs<EpisodePageDto>().episodeData
|
||||||
val data = json.decodeFromString<WatchDto>(jsonString).props.pageProps.episodeData
|
|
||||||
val subtitles = json.decodeFromString<List<SubtitleDto>>(data.subtitlesJson)
|
val subtitles = json.decodeFromString<List<SubtitleDto>>(data.subtitlesJson)
|
||||||
// val videoUrl = client.newCall(GET("$baseUrl/api/streams?episodeId=${data.episode.id}", headers)).execute().parseAs<StreamsDto>().url
|
// val videoUrl = client.newCall(GET("$baseUrl/api/streams?episodeId=${data.episode.id}", headers)).execute().parseAs<StreamsDto>().url
|
||||||
// keeping it in case the simpler solution breaks, can be hardcoded to this for now :
|
// keeping it in case the simpler solution breaks, can be hardcoded to this for now :
|
||||||
@ -165,7 +176,7 @@ class Sudatchi : AnimeHttpSource(), ConfigurableAnimeSource {
|
|||||||
videoUrl,
|
videoUrl,
|
||||||
videoNameGen = { "Sudatchi (Private IPFS Gateway) - $it" },
|
videoNameGen = { "Sudatchi (Private IPFS Gateway) - $it" },
|
||||||
subtitleList = subtitles.map {
|
subtitleList = subtitles.map {
|
||||||
Track("$baseUrl${it.url}", "${it.subtitlesName.name} (${it.subtitlesName.language})")
|
Track("$ipfsUrl${it.url}", "${it.subtitlesName.name} (${it.subtitlesName.language})")
|
||||||
}.sort(),
|
}.sort(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -233,6 +244,11 @@ class Sudatchi : AnimeHttpSource(), ConfigurableAnimeSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ============================= Utilities ==============================
|
// ============================= Utilities ==============================
|
||||||
|
private inline fun <reified T> Document.parseAs(): T {
|
||||||
|
val nextData = this.selectFirst("script#__NEXT_DATA__")!!.data()
|
||||||
|
return json.decodeFromString<PropsDto<T>>(nextData).props.pageProps
|
||||||
|
}
|
||||||
|
|
||||||
private val SharedPreferences.quality get() = getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
|
private val SharedPreferences.quality get() = getString(PREF_QUALITY_KEY, PREF_QUALITY_DEFAULT)!!
|
||||||
private val SharedPreferences.subtitles get() = getString(PREF_SUBTITLES_KEY, PREF_SUBTITLES_DEFAULT)!!
|
private val SharedPreferences.subtitles get() = getString(PREF_SUBTITLES_KEY, PREF_SUBTITLES_DEFAULT)!!
|
||||||
private val SharedPreferences.title get() = getString(PREF_TITLE_KEY, PREF_TITLE_DEFAULT)!!
|
private val SharedPreferences.title get() = getString(PREF_TITLE_KEY, PREF_TITLE_DEFAULT)!!
|
||||||
|
@ -4,16 +4,23 @@ import kotlinx.serialization.SerialName
|
|||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Genre(val name: String)
|
data class GenreDto(val name: String)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class AnimeGenreRelation(
|
data class AnimeGenreRelationDto(
|
||||||
@SerialName("Genre")
|
@SerialName("Genre")
|
||||||
val genre: Genre,
|
val genre: GenreDto,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ShortAnimeDto(
|
data class EpisodeDto(
|
||||||
|
val title: String,
|
||||||
|
val id: Int,
|
||||||
|
val number: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class AnimeDto(
|
||||||
val titleRomanji: String?,
|
val titleRomanji: String?,
|
||||||
val titleEnglish: String?,
|
val titleEnglish: String?,
|
||||||
val titleJapanese: String?,
|
val titleJapanese: String?,
|
||||||
@ -22,36 +29,46 @@ data class ShortAnimeDto(
|
|||||||
val statusId: Int,
|
val statusId: Int,
|
||||||
val imgUrl: String,
|
val imgUrl: String,
|
||||||
@SerialName("AnimeGenres")
|
@SerialName("AnimeGenres")
|
||||||
val animeGenres: List<AnimeGenreRelation>?,
|
val animeGenres: List<AnimeGenreRelationDto>?,
|
||||||
|
@SerialName("Episodes")
|
||||||
|
val episodes: List<EpisodeDto> = emptyList(),
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class HomeListDto(
|
data class HomePageDto(
|
||||||
@SerialName("AnimeSpotlight")
|
@SerialName("AnimeSpotlight")
|
||||||
val animeSpotlight: List<ShortAnimeDto>,
|
val animeSpotlight: List<AnimeDto>,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class AnimePageDto(
|
||||||
|
val animeData: AnimeDto,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class EpisodeDataDto(
|
||||||
|
val episode: EpisodeDto,
|
||||||
|
val subtitlesJson: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class EpisodePageDto(
|
||||||
|
val episodeData: EpisodeDataDto,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class PagePropsDto<T>(val pageProps: T)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class PropsDto<T>(val props: PagePropsDto<T>)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class DirectoryDto(
|
data class DirectoryDto(
|
||||||
val animes: List<ShortAnimeDto>,
|
val animes: List<AnimeDto>,
|
||||||
val page: Int,
|
val page: Int,
|
||||||
val pages: Int,
|
val pages: Int,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class Episode(
|
|
||||||
val title: String,
|
|
||||||
val id: Int,
|
|
||||||
val number: Int,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class LongAnimeDto(
|
|
||||||
val slug: String,
|
|
||||||
@SerialName("Episodes")
|
|
||||||
val episodes: List<Episode>,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class SubtitleLangDto(
|
data class SubtitleLangDto(
|
||||||
val name: String,
|
val name: String,
|
||||||
@ -65,27 +82,6 @@ data class SubtitleDto(
|
|||||||
val subtitlesName: SubtitleLangDto,
|
val subtitlesName: SubtitleLangDto,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class EpisodeDataDto(
|
|
||||||
val episode: Episode,
|
|
||||||
val subtitlesJson: String,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class PagePropsDto(
|
|
||||||
val episodeData: EpisodeDataDto,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class DataWatchDto(
|
|
||||||
val pageProps: PagePropsDto,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class WatchDto(
|
|
||||||
val props: DataWatchDto,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class FilterItemDto(
|
data class FilterItemDto(
|
||||||
val id: Int,
|
val id: Int,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user