fix: Aniplay (#452)
* en/AniPlay: many changes to video fetching and extension structure, added explicit case for Yuki * en/AniPlay: version bump
This commit is contained in:
parent
0ee26a629c
commit
896bbe57a1
3 changed files with 154 additions and 88 deletions
|
@ -2,7 +2,7 @@ ext {
|
||||||
extName = 'AniPlay'
|
extName = 'AniPlay'
|
||||||
extClass = '.AniPlay'
|
extClass = '.AniPlay'
|
||||||
themePkg = 'anilist'
|
themePkg = 'anilist'
|
||||||
overrideVersionCode = 6
|
overrideVersionCode = 7
|
||||||
}
|
}
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
apply from: "$rootDir/common.gradle"
|
||||||
|
|
|
@ -17,7 +17,6 @@ import eu.kanade.tachiyomi.lib.playlistutils.PlaylistUtils
|
||||||
import eu.kanade.tachiyomi.multisrc.anilist.AniListAnimeHttpSource
|
import eu.kanade.tachiyomi.multisrc.anilist.AniListAnimeHttpSource
|
||||||
import eu.kanade.tachiyomi.network.POST
|
import eu.kanade.tachiyomi.network.POST
|
||||||
import eu.kanade.tachiyomi.util.parallelFlatMap
|
import eu.kanade.tachiyomi.util.parallelFlatMap
|
||||||
import eu.kanade.tachiyomi.util.parallelMap
|
|
||||||
import eu.kanade.tachiyomi.util.parseAs
|
import eu.kanade.tachiyomi.util.parseAs
|
||||||
import kotlinx.serialization.SerializationException
|
import kotlinx.serialization.SerializationException
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
|
@ -183,99 +182,152 @@ class AniPlay : AniListAnimeHttpSource(), ConfigurableAnimeSource {
|
||||||
}
|
}
|
||||||
?: emptyList()
|
?: emptyList()
|
||||||
|
|
||||||
val headersWithAction =
|
|
||||||
headers.newBuilder()
|
|
||||||
// next.js stuff I guess
|
|
||||||
.add("Next-Action", getHeaderValue(baseHost, NEXT_ACTION_SOURCES_LIST))
|
|
||||||
.build()
|
|
||||||
|
|
||||||
var timeouts = 0
|
var timeouts = 0
|
||||||
var maxTimeout = 0
|
var maxTimeout = 0
|
||||||
val episodeDataList = extras.parallelFlatMap { extra ->
|
val videos = extras.parallelFlatMap { extra ->
|
||||||
val languages = mutableListOf("sub").apply {
|
val languages = mutableListOf("sub").apply {
|
||||||
if (extra.hasDub) add("dub")
|
if (extra.hasDub) add("dub")
|
||||||
}
|
}
|
||||||
languages.parallelMap { language ->
|
languages.parallelFlatMap { language ->
|
||||||
maxTimeout += 1
|
|
||||||
val epNum = if (extra.episodeNum == extra.episodeNum.toInt().toFloat()) {
|
val epNum = if (extra.episodeNum == extra.episodeNum.toInt().toFloat()) {
|
||||||
extra.episodeNum.toInt().toString() // If it has no fractional part, convert it to an integer
|
extra.episodeNum.toInt().toString()
|
||||||
} else {
|
} else {
|
||||||
extra.episodeNum.toString() // If it has a fractional part, leave it as a float
|
extra.episodeNum.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
val requestBody = "[\"$animeId\",\"${extra.source}\",\"${extra.episodeId}\",\"$epNum\",\"$language\"]"
|
|
||||||
.toRequestBody("application/json".toMediaType())
|
|
||||||
|
|
||||||
val params = mapOf(
|
val params = mapOf(
|
||||||
"host" to extra.source,
|
"host" to extra.source,
|
||||||
"ep" to epNum,
|
"ep" to epNum,
|
||||||
"type" to language,
|
"type" to language,
|
||||||
)
|
)
|
||||||
|
|
||||||
val builder = Uri.parse("$baseUrl/anime/watch/$animeId").buildUpon()
|
val builder = Uri.parse("$baseUrl/anime/watch/$animeId").buildUpon()
|
||||||
params.map { (k, v) -> builder.appendQueryParameter(k, v); }
|
params.map { (k, v) -> builder.appendQueryParameter(k, v); }
|
||||||
val url = builder.build().toString()
|
val url = builder.build()
|
||||||
|
|
||||||
|
val headersWithAction =
|
||||||
|
headers.newBuilder()
|
||||||
|
.add("Next-Action", getHeaderValue(baseHost, NEXT_ACTION_SOURCES_LIST))
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val requestBody = "[\"$animeId\",\"${extra.source}\",\"${extra.episodeId}\",\"$epNum\",\"$language\"]"
|
||||||
|
.toRequestBody("application/json".toMediaType())
|
||||||
|
|
||||||
|
val request = POST(url.toString(), headersWithAction, requestBody)
|
||||||
|
|
||||||
|
maxTimeout += 1
|
||||||
try {
|
try {
|
||||||
val request = POST(url, headersWithAction, requestBody)
|
getVideos(extra, language, request)
|
||||||
|
} catch (e: java.net.SocketTimeoutException) {
|
||||||
|
Log.e("AniPlay", "VideoList $url SocketTimeoutException", e)
|
||||||
|
timeouts++
|
||||||
|
emptyList()
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.e("AniPlay", "VideoList $url IOException", e)
|
||||||
|
emptyList()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("AniPlay", "VideoList $url Exception", e)
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (videos.isEmpty() && timeouts != 0 && maxTimeout == timeouts) {
|
||||||
|
throw Exception("Timed out")
|
||||||
|
}
|
||||||
|
|
||||||
|
return videos.sort()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getVideos(extra: EpisodeExtra, language: String, request: Request): List<Video> {
|
||||||
val response = client.newCall(request).execute()
|
val response = client.newCall(request).execute()
|
||||||
|
|
||||||
val responseString = response.body.string()
|
val responseString = response.body.string()
|
||||||
val sourcesString = extractSourcesList(responseString) ?: return@parallelMap null
|
val sourcesString = extractSourcesList(responseString) ?: return emptyList()
|
||||||
val data = sourcesString.parseAs<VideoSourceResponse>()
|
Log.i("AniPlay", "${extra.source} $language -> $sourcesString")
|
||||||
|
|
||||||
|
when (extra.source.lowercase()) {
|
||||||
|
"yuki" -> {
|
||||||
|
val data = sourcesString.parseAs<VideoSourceResponseYuki>()
|
||||||
|
return processEpisodeDataYuki(
|
||||||
|
EpisodeDataYuki(
|
||||||
|
source = extra.source,
|
||||||
|
language = language,
|
||||||
|
response = data,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
val data = sourcesString.parseAs<VideoSourceResponse>()
|
||||||
|
return processEpisodeData(
|
||||||
EpisodeData(
|
EpisodeData(
|
||||||
source = extra.source,
|
source = extra.source,
|
||||||
language = language,
|
language = language,
|
||||||
response = data,
|
response = data,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
} catch (e: java.net.SocketTimeoutException) {
|
|
||||||
timeouts += 1
|
|
||||||
null
|
|
||||||
} catch (e: IOException) {
|
|
||||||
Log.w("AniPlay", "VideoList $url IOException", e)
|
|
||||||
timeouts = -999
|
|
||||||
null // Return null to be filtered out
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w("AniPlay", "VideoList $url Exception", e)
|
|
||||||
timeouts = -999
|
|
||||||
null // Return null to be filtered out
|
|
||||||
}
|
}
|
||||||
}.filterNotNull() // Filter out null values due to errors
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (maxTimeout == timeouts && timeouts != 0) {
|
private fun processEpisodeDataYuki(episodeData: EpisodeDataYuki): List<Video> {
|
||||||
throw Exception("Timed out")
|
val defaultSource = episodeData.response.sources?.firstOrNull()
|
||||||
|
|
||||||
|
if (defaultSource == null) {
|
||||||
|
Log.e("AniPlay", "defaultSource is null (${episodeData.response})")
|
||||||
|
return emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
val videos = episodeDataList.flatMap { episodeData ->
|
val subtitles = episodeData.response.tracks
|
||||||
val defaultSource = episodeData.response.sources?.firstOrNull {
|
?.filter { it.kind?.lowercase() == "captions" }
|
||||||
it.quality in listOf("default", "auto")
|
?.map { Track(it.file, it.label ?: "Unknown") }
|
||||||
} ?: return@flatMap emptyList()
|
|
||||||
|
|
||||||
val subtitles = episodeData.response.subtitles
|
|
||||||
?.filter { it.lang != "Thumbnails" }
|
|
||||||
?.map { Track(it.url, it.lang) }
|
|
||||||
?: emptyList()
|
?: emptyList()
|
||||||
|
|
||||||
|
val serverName = getServerName(episodeData.source)
|
||||||
|
val typeName = getTypeName(episodeData.language).let {
|
||||||
|
if (it == "Sub" && subtitles.isNotEmpty()) "SoftSub" else it
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
playlistUtils.extractFromHls(
|
return playlistUtils.extractFromHls(
|
||||||
playlistUrl = defaultSource.url,
|
playlistUrl = defaultSource.url,
|
||||||
videoNameGen = { quality ->
|
videoNameGen = { quality -> "$serverName - $quality - $typeName" },
|
||||||
|
subtitleList = subtitles,
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("AniPlay", "processEpisodeDataYuki extractFromHls Error (\"$serverName - $typeName\"): $e")
|
||||||
|
}
|
||||||
|
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processEpisodeData(episodeData: EpisodeData): List<Video> {
|
||||||
|
val defaultSource = episodeData.response.sources?.firstOrNull {
|
||||||
|
it.quality in listOf("default", "auto")
|
||||||
|
} ?: return emptyList()
|
||||||
|
|
||||||
|
val subtitles = episodeData.response.subtitles
|
||||||
|
?.filter { it.lang?.lowercase() != "thumbnails" }
|
||||||
|
?.map { Track(it.url, it.lang ?: "Unk") }
|
||||||
|
?: emptyList()
|
||||||
|
|
||||||
val serverName = getServerName(episodeData.source)
|
val serverName = getServerName(episodeData.source)
|
||||||
val typeName = when {
|
val typeName = when {
|
||||||
subtitles.isNotEmpty() -> "SoftSub"
|
subtitles.isNotEmpty() -> "SoftSub"
|
||||||
else -> getTypeName(episodeData.language)
|
else -> getTypeName(episodeData.language)
|
||||||
}
|
}
|
||||||
"$serverName - $quality - $typeName"
|
|
||||||
},
|
try {
|
||||||
|
return playlistUtils.extractFromHls(
|
||||||
|
playlistUrl = defaultSource.url,
|
||||||
|
videoNameGen = { quality -> "$serverName - $quality - $typeName" },
|
||||||
subtitleList = subtitles,
|
subtitleList = subtitles,
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("AniPlay", "extractFromHls Error: $e")
|
Log.e("AniPlay", "processEpisodeData extractFromHls Error (\"$serverName - $typeName\"): $e")
|
||||||
emptyList()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return videos.sort()
|
return emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun List<Video>.sort(): List<Video> {
|
override fun List<Video>.sort(): List<Video> {
|
||||||
|
@ -431,7 +483,7 @@ class AniPlay : AniListAnimeHttpSource(), ConfigurableAnimeSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getTypeName(value: String): String {
|
private fun getTypeName(value: String): String {
|
||||||
val index = PREF_TYPE_ENTRY_VALUES.indexOf(value)
|
val index = PREF_TYPE_ENTRY_VALUES.indexOf(value.lowercase())
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
return "Other"
|
return "Other"
|
||||||
}
|
}
|
||||||
|
@ -456,8 +508,8 @@ class AniPlay : AniListAnimeHttpSource(), ConfigurableAnimeSource {
|
||||||
private const val PREF_DOMAIN_DEFAULT = "aniplaynow.live"
|
private const val PREF_DOMAIN_DEFAULT = "aniplaynow.live"
|
||||||
|
|
||||||
private const val PREF_SERVER_KEY = "server"
|
private const val PREF_SERVER_KEY = "server"
|
||||||
private val PREF_SERVER_ENTRIES = arrayOf("Kuro", "Yuki", "Yuno")
|
private val PREF_SERVER_ENTRIES = arrayOf("Kuro", "Anya", "Yuki")
|
||||||
private val PREF_SERVER_ENTRY_VALUES = arrayOf("kuro", "yuki", "yuno")
|
private val PREF_SERVER_ENTRY_VALUES = arrayOf("kuro", "anya", "yuki")
|
||||||
private const val PREF_SERVER_DEFAULT = "kuro"
|
private const val PREF_SERVER_DEFAULT = "kuro"
|
||||||
|
|
||||||
private const val PREF_QUALITY_KEY = "quality"
|
private const val PREF_QUALITY_KEY = "quality"
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package eu.kanade.tachiyomi.animeextension.en.aniplay
|
package eu.kanade.tachiyomi.animeextension.en.aniplay
|
||||||
|
|
||||||
import kotlinx.serialization.SerialName
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@ -22,20 +21,6 @@ data class EpisodeListResponse(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class VideoSourceRequest(
|
|
||||||
val source: String,
|
|
||||||
|
|
||||||
@SerialName("episodeid")
|
|
||||||
val episodeId: String,
|
|
||||||
|
|
||||||
@SerialName("episodenum")
|
|
||||||
val episodeNum: String,
|
|
||||||
|
|
||||||
@SerialName("subtype")
|
|
||||||
val subType: String,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class VideoSourceResponse(
|
data class VideoSourceResponse(
|
||||||
val sources: List<Source>?,
|
val sources: List<Source>?,
|
||||||
|
@ -44,13 +29,35 @@ data class VideoSourceResponse(
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Source(
|
data class Source(
|
||||||
val url: String,
|
val url: String,
|
||||||
val quality: String,
|
val quality: String?,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Subtitle(
|
data class Subtitle(
|
||||||
val url: String,
|
val url: String,
|
||||||
val lang: String,
|
val lang: String?,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class VideoSourceResponseYuki(
|
||||||
|
val sources: List<Source>?,
|
||||||
|
val tracks: List<Subtitle>?,
|
||||||
|
val anilistID: Int?,
|
||||||
|
val malID: Int?,
|
||||||
|
) {
|
||||||
|
@Serializable
|
||||||
|
data class Source(
|
||||||
|
val url: String,
|
||||||
|
val type: String?,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Subtitle(
|
||||||
|
val file: String,
|
||||||
|
val label: String?,
|
||||||
|
val kind: String?,
|
||||||
|
val default: Boolean?,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,3 +75,10 @@ data class EpisodeData(
|
||||||
val language: String,
|
val language: String,
|
||||||
val response: VideoSourceResponse,
|
val response: VideoSourceResponse,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class EpisodeDataYuki(
|
||||||
|
val source: String,
|
||||||
|
val language: String,
|
||||||
|
val response: VideoSourceResponseYuki,
|
||||||
|
)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue