komikku/app/src/main/java/exh/recs/RecommendsPagingSource.kt

306 lines
11 KiB
Kotlin
Raw Normal View History

package exh.recs
Merge Latest and Browse into one screen (#7921) * Merge Latest and Browse into one * Add back Latest button * Change context to IO instead of launching a job * Use loading screen when loading initial page (cherry picked from commit cc6aef693e1a15f695ffa7eebd968004a6557010) # Conflicts: # app/src/main/java/eu/kanade/data/source/SourceRepositoryImpl.kt # app/src/main/java/eu/kanade/presentation/browse/BrowseLatestScreen.kt # app/src/main/java/eu/kanade/presentation/browse/BrowseSourceScreen.kt # app/src/main/java/eu/kanade/presentation/browse/SourceSearchScreen.kt # app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt # app/src/main/java/eu/kanade/presentation/browse/components/BrowseLatestToolbar.kt # app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceComfortableGrid.kt # app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceToolbar.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SourceSearchController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesTab.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowsePagingSource.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/latest/LatestUpdatesController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/latest/LatestUpdatesPresenter.kt
2022-09-03 16:16:30 +02:00
import eu.kanade.data.source.NoResultsException
import eu.kanade.data.source.SourcePagingSource
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.track.interactor.GetTracks
import eu.kanade.tachiyomi.data.track.TrackManager
2020-11-30 02:38:22 +01:00
import eu.kanade.tachiyomi.network.GET
2021-06-10 23:20:54 +02:00
import eu.kanade.tachiyomi.network.NetworkHelper
2020-11-30 02:38:22 +01:00
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.await
2021-06-10 23:20:54 +02:00
import eu.kanade.tachiyomi.network.parseAs
Merge Latest and Browse into one screen (#7921) * Merge Latest and Browse into one * Add back Latest button * Change context to IO instead of launching a job * Use loading screen when loading initial page (cherry picked from commit cc6aef693e1a15f695ffa7eebd968004a6557010) # Conflicts: # app/src/main/java/eu/kanade/data/source/SourceRepositoryImpl.kt # app/src/main/java/eu/kanade/presentation/browse/BrowseLatestScreen.kt # app/src/main/java/eu/kanade/presentation/browse/BrowseSourceScreen.kt # app/src/main/java/eu/kanade/presentation/browse/SourceSearchScreen.kt # app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt # app/src/main/java/eu/kanade/presentation/browse/components/BrowseLatestToolbar.kt # app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceComfortableGrid.kt # app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceToolbar.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SourceSearchController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesTab.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowsePagingSource.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/latest/LatestUpdatesController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/latest/LatestUpdatesPresenter.kt
2022-09-03 16:16:30 +02:00
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.util.system.logcat
import exh.util.MangaType
import exh.util.mangaType
import kotlinx.serialization.json.JsonArray
2022-10-09 01:36:14 +02:00
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.put
import logcat.LogPriority
2022-10-09 01:36:14 +02:00
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
2021-06-10 23:20:54 +02:00
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
2021-06-10 23:20:54 +02:00
abstract class API(val endpoint: String) {
val client by lazy {
Injekt.get<NetworkHelper>().client
}
abstract suspend fun getRecsBySearch(search: String): List<SManga>
abstract suspend fun getRecsById(id: String): List<SManga>
}
2022-10-09 01:36:14 +02:00
class MyAnimeList : API("https://api.jikan.moe/v4/") {
override suspend fun getRecsById(id: String): List<SManga> {
2022-10-09 01:36:14 +02:00
val apiUrl = endpoint.toHttpUrl()
.newBuilder()
2020-11-30 02:38:22 +01:00
.addPathSegment("manga")
.addPathSegment(id)
.addPathSegment("recommendations")
.build()
2022-10-09 01:36:14 +02:00
val data = client.newCall(GET(apiUrl)).await().parseAs<JsonObject>()
return data["data"]!!.jsonArray
.map { it.jsonObject["entry"]!!.jsonObject }
.map { rec ->
logcat { "MYANIMELIST > RECOMMENDATION: " + rec["title"]!!.jsonPrimitive.content }
SManga(
title = rec["title"]!!.jsonPrimitive.content,
url = rec["url"]!!.jsonPrimitive.content,
thumbnail_url = rec["images"]
?.let(JsonElement::jsonObject)
?.let(::getImage),
initialized = true,
)
}
2022-10-09 01:36:14 +02:00
}
fun getImage(imageObject: JsonObject): String? {
return imageObject["webp"]
?.jsonObject
?.get("image_url")
?.jsonPrimitive
?.contentOrNull
?: imageObject["jpg"]
?.jsonObject
?.get("image_url")
?.jsonPrimitive
?.contentOrNull
}
override suspend fun getRecsBySearch(search: String): List<SManga> {
2022-10-09 01:36:14 +02:00
val url = endpoint.toHttpUrl()
.newBuilder()
2020-11-30 02:38:22 +01:00
.addPathSegment("manga")
.addQueryParameter("q", search)
.build()
2021-06-10 23:20:54 +02:00
val data = client.newCall(GET(url)).await()
.parseAs<JsonObject>()
2022-10-09 01:36:14 +02:00
return getRecsById(data["data"]!!.jsonArray.first().jsonObject["mal_id"]!!.jsonPrimitive.content)
}
}
2020-10-29 20:28:48 +01:00
class Anilist : API("https://graphql.anilist.co/") {
private fun countOccurrence(arr: JsonArray, search: String): Int {
return arr.count {
val synonym = it.jsonPrimitive.content
synonym.contains(search, true)
}
}
private fun languageContains(obj: JsonObject, language: String, search: String): Boolean {
return obj["title"]?.jsonObject?.get(language)?.jsonPrimitive?.contentOrNull?.contains(search, true) == true
}
private fun getTitle(obj: JsonObject): String {
val titleObj = obj["title"]!!.jsonObject
val english = titleObj["english"]?.jsonPrimitive?.contentOrNull
val romaji = titleObj["romaji"]?.jsonPrimitive?.contentOrNull
val native = titleObj["native"]?.jsonPrimitive?.contentOrNull
val synonym = obj["synonyms"]!!.jsonArray.getOrNull(0)?.jsonPrimitive?.contentOrNull
val isJP = obj["countryOfOrigin"]?.jsonPrimitive?.contentOrNull == "JP"
return when {
!english.isNullOrBlank() -> english
isJP && !romaji.isNullOrBlank() -> romaji
!synonym.isNullOrBlank() -> synonym
!isJP && !romaji.isNullOrBlank() -> romaji
else -> native ?: "NO NAME FOUND"
}
}
private suspend fun getRecs(
query: String,
variables: JsonObject,
queryParam: String? = null,
filter: List<JsonElement>.() -> List<JsonElement> = { this }
): List<SManga> {
val payload = buildJsonObject {
put("query", query)
put("variables", variables)
}
val payloadBody = payload.toString().toRequestBody("application/json; charset=utf-8".toMediaType())
val data = client.newCall(POST(endpoint, body = payloadBody)).await()
.parseAs<JsonObject>()
val media = data["data"]!!
.jsonObject["Page"]!!
.jsonObject["media"]!!
.jsonArray
.ifEmpty { throw Exception("'$queryParam' not found") }
.filter()
return media.flatMap { it.jsonObject["recommendations"]!!.jsonObject["edges"]!!.jsonArray }.map {
val rec = it.jsonObject["node"]!!.jsonObject["mediaRecommendation"]!!.jsonObject
val recTitle = getTitle(rec)
logcat { "ANILIST > RECOMMENDATION: $recTitle" }
SManga(
title = recTitle,
thumbnail_url = rec["coverImage"]!!.jsonObject["large"]!!.jsonPrimitive.content,
initialized = true,
url = rec["siteUrl"]!!.jsonPrimitive.content,
)
}
}
override suspend fun getRecsById(id: String): List<SManga> {
val query =
"""
|query Recommendations(${'$'}id: Int!) {
|Page {
|media(id: ${'$'}id, type: MANGA) {
|recommendations {
|edges {
|node {
|mediaRecommendation {
|countryOfOrigin
|siteUrl
|title {
|romaji
|english
|native
|}
|synonyms
|coverImage {
|large
|}
|}
|}
|}
|}
|}
|}
|}
|
""".trimMargin()
val variables = buildJsonObject {
put("id", id)
}
return getRecs(
query = query,
variables = variables,
)
}
override suspend fun getRecsBySearch(search: String): List<SManga> {
val query =
"""
|query Recommendations(${'$'}search: String!) {
|Page {
|media(search: ${'$'}search, type: MANGA) {
|title {
|romaji
|english
|native
|}
|synonyms
|recommendations {
|edges {
|node {
|mediaRecommendation {
|countryOfOrigin
|siteUrl
|title {
|romaji
|english
|native
|}
|synonyms
|coverImage {
|large
|}
|}
|}
|}
|}
|}
|}
|}
Update linter (cherry picked from commit f0eb42e72d1e267049777a303bd97d96517a9a1f) # Conflicts: # app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupRestore.kt # app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/Backup.kt # app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupManga.kt # app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/models/Backup.kt # app/src/main/java/eu/kanade/tachiyomi/extension/model/Extension.kt # app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt # app/src/main/java/eu/kanade/tachiyomi/ui/base/changehandler/OneWayFadeChangeHandler.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchPresenter.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/SelectionHeader.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceItem.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcePresenter.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceFilterSheet.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt # app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt # app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt # app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt # app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPageSheet.kt # app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt # app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt # app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ReaderPage.kt # app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt # app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt
2022-04-08 21:30:30 +02:00
|
""".trimMargin()
val variables = buildJsonObject {
put("search", search)
}
return getRecs(
queryParam = search,
query = query,
variables = variables,
filter = {
filter {
val jsonObject = it.jsonObject
languageContains(jsonObject, "romaji", search) ||
languageContains(jsonObject, "english", search) ||
languageContains(jsonObject, "native", search) ||
countOccurrence(jsonObject["synonyms"]!!.jsonArray, search) > 0
}
}
)
}
}
open class RecommendsPagingSource(
Merge Latest and Browse into one screen (#7921) * Merge Latest and Browse into one * Add back Latest button * Change context to IO instead of launching a job * Use loading screen when loading initial page (cherry picked from commit cc6aef693e1a15f695ffa7eebd968004a6557010) # Conflicts: # app/src/main/java/eu/kanade/data/source/SourceRepositoryImpl.kt # app/src/main/java/eu/kanade/presentation/browse/BrowseLatestScreen.kt # app/src/main/java/eu/kanade/presentation/browse/BrowseSourceScreen.kt # app/src/main/java/eu/kanade/presentation/browse/SourceSearchScreen.kt # app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt # app/src/main/java/eu/kanade/presentation/browse/components/BrowseLatestToolbar.kt # app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceComfortableGrid.kt # app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceToolbar.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SourceSearchController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesTab.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowsePagingSource.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/latest/LatestUpdatesController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/latest/LatestUpdatesPresenter.kt
2022-09-03 16:16:30 +02:00
source: CatalogueSource,
2020-10-29 20:28:48 +01:00
private val manga: Manga,
private val smart: Boolean = true,
Update linter (cherry picked from commit f0eb42e72d1e267049777a303bd97d96517a9a1f) # Conflicts: # app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupRestore.kt # app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/Backup.kt # app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupManga.kt # app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/models/Backup.kt # app/src/main/java/eu/kanade/tachiyomi/extension/model/Extension.kt # app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt # app/src/main/java/eu/kanade/tachiyomi/ui/base/changehandler/OneWayFadeChangeHandler.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchPresenter.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/SelectionHeader.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceItem.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcePresenter.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceFilterSheet.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt # app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt # app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt # app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt # app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPageSheet.kt # app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt # app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt # app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ReaderPage.kt # app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt # app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt
2022-04-08 21:30:30 +02:00
private var preferredApi: API = API.MYANIMELIST,
Merge Latest and Browse into one screen (#7921) * Merge Latest and Browse into one * Add back Latest button * Change context to IO instead of launching a job * Use loading screen when loading initial page (cherry picked from commit cc6aef693e1a15f695ffa7eebd968004a6557010) # Conflicts: # app/src/main/java/eu/kanade/data/source/SourceRepositoryImpl.kt # app/src/main/java/eu/kanade/presentation/browse/BrowseLatestScreen.kt # app/src/main/java/eu/kanade/presentation/browse/BrowseSourceScreen.kt # app/src/main/java/eu/kanade/presentation/browse/SourceSearchScreen.kt # app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt # app/src/main/java/eu/kanade/presentation/browse/components/BrowseLatestToolbar.kt # app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceComfortableGrid.kt # app/src/main/java/eu/kanade/presentation/browse/components/BrowseSourceToolbar.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SourceSearchController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcesTab.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowsePagingSource.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourcePresenter.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/latest/LatestUpdatesController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/latest/LatestUpdatesPresenter.kt
2022-09-03 16:16:30 +02:00
) : SourcePagingSource(source) {
val getTracks: GetTracks by injectLazy()
override suspend fun requestNextPage(currentPage: Int): MangasPage {
if (smart) preferredApi = if (manga.mangaType() != MangaType.TYPE_MANGA) API.ANILIST else preferredApi
val apiList = API_MAP.toList().sortedByDescending { it.first == preferredApi }
val tracks = getTracks.await(manga.id)
val recs = apiList.firstNotNullOfOrNull { (key, api) ->
try {
val id = when (key) {
API.MYANIMELIST -> tracks.find { it.syncId == TrackManager.MYANIMELIST }?.remoteId
API.ANILIST -> tracks.find { it.syncId == TrackManager.ANILIST }?.remoteId
}
val recs = if (id != null) {
api.getRecsById(id.toString())
} else {
api.getRecsBySearch(manga.ogTitle)
}
2022-10-09 01:36:14 +02:00
logcat { key.toString() + " > Results: " + recs.size }
recs.ifEmpty { null }
} catch (e: Exception) {
logcat(LogPriority.ERROR, e) { key.toString() }
null
}
2022-10-09 01:36:14 +02:00
} ?: throw NoResultsException()
return MangasPage(recs, false)
}
companion object {
val API_MAP = mapOf(
API.MYANIMELIST to MyAnimeList(),
Update linter (cherry picked from commit f0eb42e72d1e267049777a303bd97d96517a9a1f) # Conflicts: # app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupRestore.kt # app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/Backup.kt # app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupManga.kt # app/src/main/java/eu/kanade/tachiyomi/data/backup/legacy/models/Backup.kt # app/src/main/java/eu/kanade/tachiyomi/extension/model/Extension.kt # app/src/main/java/eu/kanade/tachiyomi/source/LocalSource.kt # app/src/main/java/eu/kanade/tachiyomi/ui/base/changehandler/OneWayFadeChangeHandler.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchPresenter.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/sources/SelectionHeader.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourceItem.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/SourcePresenter.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceFilterSheet.kt # app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt # app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryHolder.kt # app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt # app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt # app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt # app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPageSheet.kt # app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt # app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt # app/src/main/java/eu/kanade/tachiyomi/ui/reader/model/ReaderPage.kt # app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt # app/src/main/java/eu/kanade/tachiyomi/util/view/ViewExtensions.kt
2022-04-08 21:30:30 +02:00
API.ANILIST to Anilist(),
)
enum class API { MYANIMELIST, ANILIST }
}
}