mirror of
https://github.com/yuzono/anime-extensions.git
synced 2026-06-13 13:39:44 +00:00
Add relations and recommendations
This commit is contained in:
@@ -102,6 +102,85 @@ fun anilistLatestQuery() = """
|
||||
}
|
||||
""".toQuery()
|
||||
|
||||
// relations{edges{id relationType(version:2)node{id title{userPreferred}format type status(version:2)bannerImage coverImage{large}}}}
|
||||
fun getRelationsById() = """
|
||||
query Relations(%id: Int!) {
|
||||
Page {
|
||||
media(id: %id, type: ANIME) {
|
||||
relations {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
title {
|
||||
romaji
|
||||
english
|
||||
native
|
||||
}
|
||||
coverImage {
|
||||
extraLarge
|
||||
large
|
||||
}
|
||||
description
|
||||
status
|
||||
tags {
|
||||
name
|
||||
}
|
||||
genres
|
||||
studios {
|
||||
nodes {
|
||||
name
|
||||
}
|
||||
}
|
||||
countryOfOrigin
|
||||
isAdult
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""".toQuery()
|
||||
|
||||
fun getRecommendationsById() = """
|
||||
query Recommendations(%id: Int!, %page: Int) {
|
||||
Page {
|
||||
media(id: %id, type: ANIME) {
|
||||
recommendations(page:%page, sort:[RATING_DESC,ID]) {
|
||||
edges {
|
||||
node {
|
||||
mediaRecommendation {
|
||||
id
|
||||
title {
|
||||
romaji
|
||||
english
|
||||
native
|
||||
}
|
||||
coverImage {
|
||||
extraLarge
|
||||
large
|
||||
}
|
||||
description
|
||||
status
|
||||
tags {
|
||||
name
|
||||
}
|
||||
genres
|
||||
studios {
|
||||
nodes {
|
||||
name
|
||||
}
|
||||
}
|
||||
countryOfOrigin
|
||||
isAdult
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""".toQuery()
|
||||
|
||||
fun getDetailsQuery() = """
|
||||
query media(%id: Int) {
|
||||
Media(id: %id, isAdult: false) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.animeextension.all.torrentioanime
|
||||
import android.app.Application
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.preference.EditTextPreference
|
||||
import androidx.preference.ListPreference
|
||||
@@ -25,6 +26,8 @@ import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
import extensions.utils.getPreferencesLazy
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.add
|
||||
@@ -90,42 +93,8 @@ class Torrentio : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
|
||||
val animeList = mediaList
|
||||
.filterNot { (it?.countryOfOrigin == "CN" || it?.isAdult == true) && isLatestQuery }
|
||||
.map { media ->
|
||||
val anime = SAnime.create().apply {
|
||||
url = media?.id.toString()
|
||||
title = when (preferences.getString(PREF_TITLE_KEY, "romaji")) {
|
||||
"romaji" -> media?.title?.romaji.toString()
|
||||
"english" -> (media?.title?.english?.takeIf { it.isNotBlank() } ?: media?.title?.romaji).toString()
|
||||
"native" -> media?.title?.native.toString()
|
||||
else -> ""
|
||||
}
|
||||
thumbnail_url = media?.coverImage?.extraLarge
|
||||
description = media?.description
|
||||
?.replace(Regex("<br><br>"), "\n")
|
||||
?.replace(Regex("<.*?>"), "")
|
||||
?: "No Description"
|
||||
|
||||
status = when (media?.status) {
|
||||
"RELEASING" -> SAnime.ONGOING
|
||||
"FINISHED" -> SAnime.COMPLETED
|
||||
"HIATUS" -> SAnime.ON_HIATUS
|
||||
"NOT_YET_RELEASED" -> SAnime.LICENSED
|
||||
else -> SAnime.UNKNOWN
|
||||
}
|
||||
|
||||
// Extracting tags
|
||||
val tagsList = media?.tags?.mapNotNull { it.name }.orEmpty()
|
||||
// Extracting genres
|
||||
val genresList = media?.genres.orEmpty()
|
||||
genre = (tagsList + genresList).toSet().sorted().joinToString()
|
||||
|
||||
// Extracting studios
|
||||
val studiosList = media?.studios?.nodes?.mapNotNull { it.name }.orEmpty()
|
||||
author = studiosList.sorted().joinToString()
|
||||
|
||||
initialized = true
|
||||
}
|
||||
anime
|
||||
.mapNotNull { media ->
|
||||
media?.toSAnime(preferences.getString(PREF_TITLE_KEY, "romaji"))
|
||||
}
|
||||
|
||||
return AnimesPage(animeList, hasNextPage)
|
||||
@@ -235,6 +204,75 @@ class Torrentio : ConfigurableAnimeSource, AnimeHttpSource() {
|
||||
|
||||
override fun searchAnimeParse(response: Response) = popularAnimeParse(response)
|
||||
|
||||
// ============================== Suggestions ===============================
|
||||
override fun relatedAnimeListRequest(anime: SAnime): Request {
|
||||
val variables = buildJsonObject {
|
||||
put("id", anime.url)
|
||||
}.toString()
|
||||
|
||||
return makeGraphQLRequest(getRelationsById(), variables)
|
||||
}
|
||||
|
||||
override fun relatedAnimeListParse(response: Response): List<SAnime> {
|
||||
val jsonData = response.body.string()
|
||||
val metaData = json.decodeFromString<AnilistMeta>(jsonData)
|
||||
val mediaList = metaData.data?.page?.media
|
||||
?.mapNotNull { it.relations?.edges }?.flatten()
|
||||
?.mapNotNull { it.node }
|
||||
?: emptyList()
|
||||
|
||||
return mediaList
|
||||
.filterNot { it.countryOfOrigin == "CN" || it.isAdult }
|
||||
.map { media ->
|
||||
media.toSAnime(preferences.getString(PREF_TITLE_KEY, "romaji"))
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getRelatedAnimeListBySearch(
|
||||
anime: SAnime,
|
||||
pushResults: suspend (relatedAnime: Pair<String, List<SAnime>>, completed: Boolean) -> Unit,
|
||||
) {
|
||||
coroutineScope {
|
||||
(1..3).map { page ->
|
||||
launch {
|
||||
runCatching {
|
||||
client.newCall(recommendationsRequest(anime, page))
|
||||
.awaitSuccess()
|
||||
.use(::recommendationsParse)
|
||||
}
|
||||
.onSuccess { if (it.isNotEmpty()) pushResults(Pair("${anime.title}-$page", it), false) }
|
||||
.onFailure { e ->
|
||||
Log.e(this::class.simpleName, "## getRelatedAnimeListBySearch: $e")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun recommendationsRequest(anime: SAnime, page: Int): Request {
|
||||
val variables = buildJsonObject {
|
||||
put("id", anime.url)
|
||||
put("page", page)
|
||||
}.toString()
|
||||
|
||||
return makeGraphQLRequest(getRecommendationsById(), variables)
|
||||
}
|
||||
|
||||
private fun recommendationsParse(response: Response): List<SAnime> {
|
||||
val jsonData = response.body.string()
|
||||
val metaData = json.decodeFromString<AnilistMeta>(jsonData)
|
||||
val mediaList = metaData.data?.page?.media
|
||||
?.mapNotNull { it.recommendations?.edges }?.flatten()
|
||||
?.mapNotNull { it.node.mediaRecommendation }
|
||||
?: emptyList()
|
||||
|
||||
return mediaList
|
||||
.filterNot { it.countryOfOrigin == "CN" || it.isAdult }
|
||||
.map { media ->
|
||||
media.toSAnime(preferences.getString(PREF_TITLE_KEY, "romaji"))
|
||||
}
|
||||
}
|
||||
|
||||
// ============================== Filters ===============================
|
||||
|
||||
override fun getFilterList(): AnimeFilterList = AniListFilters.FILTER_LIST
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package eu.kanade.tachiyomi.animeextension.all.torrentioanime.dto
|
||||
|
||||
import eu.kanade.tachiyomi.animesource.model.SAnime
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@@ -80,6 +81,68 @@ data class AnilistMedia(
|
||||
val seasonYear: Int? = null,
|
||||
val countryOfOrigin: String? = null,
|
||||
val isAdult: Boolean = false,
|
||||
val recommendations: AnilistRecommendations? = null,
|
||||
val relations: AnilistRelations? = null,
|
||||
) {
|
||||
fun toSAnime(userPreferredTitle: String?) = SAnime.create().apply {
|
||||
url = id.toString()
|
||||
title = when (userPreferredTitle) {
|
||||
"romaji" -> this@AnilistMedia.title?.romaji.toString()
|
||||
"english" -> (this@AnilistMedia.title?.english?.takeIf { it.isNotBlank() } ?: this@AnilistMedia.title?.romaji).toString()
|
||||
"native" -> this@AnilistMedia.title?.native.toString()
|
||||
else -> ""
|
||||
}
|
||||
thumbnail_url = coverImage?.extraLarge
|
||||
description = description
|
||||
?.replace(Regex("<br><br>"), "\n")
|
||||
?.replace(Regex("<.*?>"), "")
|
||||
?: "No Description"
|
||||
|
||||
status = when (this@AnilistMedia.status) {
|
||||
"RELEASING" -> SAnime.ONGOING
|
||||
"FINISHED" -> SAnime.COMPLETED
|
||||
"HIATUS" -> SAnime.ON_HIATUS
|
||||
"NOT_YET_RELEASED" -> SAnime.LICENSED
|
||||
else -> SAnime.UNKNOWN
|
||||
}
|
||||
|
||||
// Extracting tags
|
||||
val tagsList = tags?.mapNotNull { it.name }.orEmpty()
|
||||
// Extracting genres
|
||||
val genresList = genres.orEmpty()
|
||||
genre = (tagsList + genresList).toSet().sorted().joinToString()
|
||||
|
||||
// Extracting studios
|
||||
val studiosList = studios?.nodes?.mapNotNull { it.name }.orEmpty()
|
||||
author = studiosList.sorted().joinToString()
|
||||
|
||||
initialized = true
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class AnilistRelations(
|
||||
val edges: List<AnilistRelationsEdge>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class AnilistRelationsEdge(
|
||||
val node: AnilistMedia?,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class AnilistRecommendations(
|
||||
val edges: List<AnilistRecommendationEdge>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class AnilistRecommendationEdge(
|
||||
val node: MediaRecommendation,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class MediaRecommendation(
|
||||
val mediaRecommendation: AnilistMedia?,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
|
||||
Reference in New Issue
Block a user