fix(all/animeonsen): Fix anime serializer (#2388)

This commit is contained in:
Claudemirovsky
2023-10-16 12:01:47 -03:00
committed by GitHub
parent 87b10660e6
commit e07fe1e8d6
3 changed files with 108 additions and 122 deletions

View File

@ -1,12 +1,14 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.serialization)
}
ext {
extName = 'AnimeOnsen'
pkgNameSuffix = 'all.animeonsen'
extClass = '.AnimeOnsen'
extVersionCode = 6
extVersionCode = 7
libVersion = '13'
}

View File

@ -1,12 +1,12 @@
package eu.kanade.tachiyomi.animeextension.all.animeonsen
import android.app.Application
import android.content.SharedPreferences
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animeextension.all.animeonsen.dto.AnimeDetails
import eu.kanade.tachiyomi.animeextension.all.animeonsen.dto.AnimeListItem
import eu.kanade.tachiyomi.animeextension.all.animeonsen.dto.AnimeListResponse
import eu.kanade.tachiyomi.animeextension.all.animeonsen.dto.EpisodeDto
import eu.kanade.tachiyomi.animeextension.all.animeonsen.dto.SearchResponse
import eu.kanade.tachiyomi.animeextension.all.animeonsen.dto.VideoData
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
@ -21,16 +21,14 @@ import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.boolean
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Headers
import okhttp3.Request
import okhttp3.Response
import rx.Observable
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import kotlin.Exception
class AnimeOnsen : ConfigurableAnimeSource, AnimeHttpSource() {
@ -53,105 +51,43 @@ class AnimeOnsen : ConfigurableAnimeSource, AnimeHttpSource() {
.build()
}
private val preferences: SharedPreferences by lazy {
private val preferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
private val json = Json {
ignoreUnknownKeys = true
}
private val json: Json by injectLazy()
override fun headersBuilder(): Headers.Builder = Headers.Builder()
.add("user-agent", AO_USER_AGENT)
override fun headersBuilder() = Headers.Builder().add("user-agent", AO_USER_AGENT)
// ============================== Popular ===============================
// The site doesn't have a popular anime tab, so we use the home page instead (latest anime).
override fun popularAnimeRequest(page: Int): Request =
override fun popularAnimeRequest(page: Int) =
GET("$apiUrl/content/index?start=${(page - 1) * 20}&limit=20")
override fun popularAnimeParse(response: Response): AnimesPage {
val responseJson = json.decodeFromString<AnimeListResponse>(response.body.string())
val animes = responseJson.content.map {
it.toSAnime()
}
val responseJson = response.parseAs<AnimeListResponse>()
val animes = responseJson.content.map { it.toSAnime() }
// we can't (easily) serialize this thing because it returns a array with
// two types: a boolean and a integer.
val hasNextPage = responseJson.cursor.next.firstOrNull()?.jsonPrimitive?.boolean == true
return AnimesPage(animes, hasNextPage)
}
// ============================== Episodes ==============================
override fun episodeListParse(response: Response): List<SEpisode> {
val contentId = response.request.url.toString().substringBeforeLast("/episodes")
.substringAfterLast("/")
val responseJson = json.decodeFromString<JsonObject>(response.body.string())
return responseJson.keys.map { epNum ->
SEpisode.create().apply {
url = "$contentId/video/$epNum"
episode_number = epNum.toFloat()
val episodeName =
responseJson[epNum]!!.jsonObject["contentTitle_episode_en"]!!.jsonPrimitive.content
name = "Episode $epNum: $episodeName"
}
}.sortedByDescending { it.episode_number }
}
override fun episodeListRequest(anime: SAnime): Request {
return GET("$apiUrl/content/${anime.url}/episodes")
}
// ============================ Video Links =============================
override fun videoListParse(response: Response): List<Video> {
val videoData = json.decodeFromString<VideoData>(response.body.string())
val videoUrl = videoData.uri.stream
val subtitleLangs = videoData.metadata.subtitles
val headers = Headers.headersOf(
"referer",
baseUrl,
"user-agent",
AO_USER_AGENT,
)
val video = try {
val subtitles = videoData.uri.subtitles.keys.toList().sortSubs().map {
val lang = subtitleLangs[it]!!.jsonPrimitive.content
val url = videoData.uri.subtitles[it]!!.jsonPrimitive.content
Track(url, lang)
}
Video(videoUrl, "Default (720p)", videoUrl, headers = headers, subtitleTracks = subtitles)
} catch (e: Error) {
Video(videoUrl, "Default (720p)", videoUrl, headers = headers)
}
return listOf(video)
}
override fun videoListRequest(episode: SEpisode) = GET("$apiUrl/content/${episode.url}")
override fun videoUrlParse(response: Response) = throw Exception("not used")
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int) = throw Exception("not used")
override fun latestUpdatesParse(response: Response) = throw Exception("not used")
// =============================== Search ===============================
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList) =
GET("$apiUrl/search/$query")
override fun searchAnimeParse(response: Response): AnimesPage {
val searchResult = json.decodeFromString<SearchResponse>(response.body.string()).result
val results = searchResult.map {
it.toSAnime()
}
val searchResult = response.parseAs<SearchResponse>().result
val results = searchResult.map { it.toSAnime() }
return AnimesPage(results, false)
}
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList) = GET("$apiUrl/search/$query")
// =========================== Anime Details ============================
override fun animeDetailsParse(response: Response): SAnime {
val details = json.decodeFromString<AnimeDetails>(response.body.string())
val anime = SAnime.create().apply {
url = details.content_id
title = details.content_title ?: details.content_title_en!!
status = parseStatus(details.mal_data?.status)
author = details.mal_data?.studios?.joinToString { it.name }
genre = details.mal_data?.genres?.joinToString { it.name }
description = details.mal_data?.synopsis
thumbnail_url = "https://api.animeonsen.xyz/v4/image/210x300/${details.content_id}"
}
return anime
}
override fun fetchAnimeDetails(anime: SAnime): Observable<SAnime> {
return client.newCall(GET("$apiUrl/content/${anime.url}/extensive"))
.asObservableSuccess()
@ -160,22 +96,63 @@ class AnimeOnsen : ConfigurableAnimeSource, AnimeHttpSource() {
}
}
override fun animeDetailsRequest(anime: SAnime): Request {
return GET("$baseUrl/details/${anime.url}")
override fun animeDetailsRequest(anime: SAnime) = GET("$baseUrl/details/${anime.url}")
override fun animeDetailsParse(response: Response) = SAnime.create().apply {
val details = response.parseAs<AnimeDetails>()
url = details.content_id
title = details.content_title ?: details.content_title_en!!
status = parseStatus(details.mal_data?.status)
author = details.mal_data?.studios?.joinToString { it.name }
genre = details.mal_data?.genres?.joinToString { it.name }
description = details.mal_data?.synopsis
thumbnail_url = "$apiUrl/image/210x300/${details.content_id}"
}
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int) = throw Exception("not used")
override fun latestUpdatesParse(response: Response) = throw Exception("not used")
// ============================== Episodes ==============================
override fun episodeListRequest(anime: SAnime) = GET("$apiUrl/content/${anime.url}/episodes")
override fun episodeListParse(response: Response): List<SEpisode> {
val contentId = response.request.url.toString().substringBeforeLast("/episodes")
.substringAfterLast("/")
val responseJson = response.parseAs<Map<String, EpisodeDto>>()
return responseJson.map { (epNum, item) ->
SEpisode.create().apply {
url = "$contentId/video/$epNum"
episode_number = epNum.toFloat()
name = "Episode $epNum: ${item.name}"
}
}.sortedByDescending { it.episode_number }
}
// ============================ Video Links =============================
override fun videoListParse(response: Response): List<Video> {
val videoData = response.parseAs<VideoData>()
val videoUrl = videoData.uri.stream
val subtitleLangs = videoData.metadata.subtitles
val headers = headersBuilder().add("referer", baseUrl).build()
val subs = videoData.uri.subtitles.sortSubs().map { (langPrefix, subUrl) ->
val language = subtitleLangs[langPrefix]!!
Track(subUrl, language)
}
val video = Video(videoUrl, "Default (720p)", videoUrl, headers, subtitleTracks = subs)
return listOf(video)
}
override fun videoListRequest(episode: SEpisode) = GET("$apiUrl/content/${episode.url}")
override fun videoUrlParse(response: Response) = throw Exception("not used")
// ============================== Settings ==============================
override fun setupPreferenceScreen(screen: PreferenceScreen) {
val subLangPref = ListPreference(screen.context).apply {
ListPreference(screen.context).apply {
key = PREF_SUB_KEY
title = PREF_SUB_TITLE
entries = PREF_SUB_ENTRIES
entryValues = PREF_SUB_VALUES
setDefaultValue("en-US")
setDefaultValue(PREF_SUB_DEFAULT)
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
@ -184,11 +161,13 @@ class AnimeOnsen : ConfigurableAnimeSource, AnimeHttpSource() {
val entry = entryValues[index] as String
preferences.edit().putString(key, entry).commit()
}
}
screen.addPreference(subLangPref)
}.also(screen::addPreference)
}
// ============================= Utilities ==============================
private inline fun <reified T> Response.parseAs(): T {
return use { it.body.string() }.let(json::decodeFromString)
}
private fun parseStatus(statusString: String?): Int {
return when (statusString?.trim()) {
@ -200,28 +179,22 @@ class AnimeOnsen : ConfigurableAnimeSource, AnimeHttpSource() {
private fun AnimeListItem.toSAnime() = SAnime.create().apply {
url = content_id
title = content_title ?: content_title_en!!
thumbnail_url = "https://api.animeonsen.xyz/v4/image/210x300/$content_id"
thumbnail_url = "$apiUrl/image/210x300/$content_id"
}
private fun List<String>.sortSubs(): List<String> {
val language = preferences.getString(PREF_SUB_KEY, "en-US")
val newList = mutableListOf<String>()
var preferred = 0
for (key in this) {
if (key == language) {
newList.add(preferred, key)
preferred++
} else {
newList.add(key)
}
}
return newList
private fun Map<String, String>.sortSubs(): List<Map.Entry<String, String>> {
val sub = preferences.getString(PREF_SUB_KEY, PREF_SUB_DEFAULT)!!
return entries.sortedWith(
compareBy { it.key.contains(sub) },
).reversed()
}
}
const val AO_USER_AGENT = "Aniyomi/app (mobile)"
private const val PREF_SUB_KEY = "preferred_subLang"
private const val PREF_SUB_TITLE = "Preferred sub language"
const val PREF_SUB_DEFAULT = "en-US"
private val PREF_SUB_ENTRIES = arrayOf(
"العربية", "Deutsch", "English", "Español (Spain)",
"Español (Latin)", "Français", "Italiano",

View File

@ -1,8 +1,12 @@
package eu.kanade.tachiyomi.animeextension.all.animeonsen.dto
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.JsonTransformingSerializer
@Serializable
data class AnimeListResponse(
@ -18,18 +22,23 @@ data class AnimeListItem(
)
@Serializable
data class AnimeListCursor(
val next: JsonArray,
)
data class AnimeListCursor(val next: JsonArray)
@Serializable
data class AnimeDetails(
val content_id: String,
val content_title: String?,
val content_title_en: String?,
@Serializable(with = MalSerializer::class)
val mal_data: MalData?,
)
@Serializable
data class EpisodeDto(
@SerialName("contentTitle_episode_en")
val name: String,
)
@Serializable
data class MalData(
val genres: List<Genre>?,
@ -39,14 +48,10 @@ data class MalData(
)
@Serializable
data class Genre(
val name: String,
)
data class Genre(val name: String)
@Serializable
data class Studio(
val name: String,
)
data class Studio(val name: String)
@Serializable
data class VideoData(
@ -55,14 +60,12 @@ data class VideoData(
)
@Serializable
data class MetaData(
val subtitles: JsonObject,
)
data class MetaData(val subtitles: Map<String, String>)
@Serializable
data class StreamData(
val stream: String,
val subtitles: JsonObject,
val subtitles: Map<String, String>,
)
@Serializable
@ -70,3 +73,11 @@ data class SearchResponse(
val status: Int,
val result: List<AnimeListItem>,
)
object MalSerializer : JsonTransformingSerializer<MalData>(MalData.serializer()) {
override fun transformDeserialize(element: JsonElement): JsonElement =
when (element) {
is JsonPrimitive -> JsonObject(emptyMap())
else -> element
}
}