diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/Anilist.kt b/app/src/main/java/ani/dantotsu/connections/anilist/Anilist.kt index 117b2fa8..85979714 100644 --- a/app/src/main/java/ani/dantotsu/connections/anilist/Anilist.kt +++ b/app/src/main/java/ani/dantotsu/connections/anilist/Anilist.kt @@ -3,6 +3,7 @@ package ani.dantotsu.connections.anilist import android.content.ActivityNotFoundException import android.content.Context import android.net.Uri +import android.util.Log import androidx.browser.customtabs.CustomTabsIntent import ani.dantotsu.R import ani.dantotsu.client @@ -128,7 +129,6 @@ object Anilist { toast("Rate limited. Try after ${rateLimitReset - (System.currentTimeMillis() / 1000)} seconds") throw Exception("Rate limited after ${rateLimitReset - (System.currentTimeMillis() / 1000)} seconds") } - val data = mapOf( "query" to query, "variables" to variables @@ -147,6 +147,8 @@ object Anilist { data = data, cacheTime = cache ?: 10 ) + val remaining = json.headers["X-RateLimit-Remaining"]?.toIntOrNull() ?: -1 + Log.d("AnilistQuery", "Remaining requests: $remaining") if (json.code == 429) { val retry = json.headers["Retry-After"]?.toIntOrNull() ?: -1 val passedLimitReset = json.headers["X-RateLimit-Reset"]?.toLongOrNull() ?: 0 diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt index 14bb86b7..92cfc52d 100644 --- a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt +++ b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt @@ -1,6 +1,5 @@ package ani.dantotsu.connections.anilist -import android.app.Activity import android.util.Base64 import ani.dantotsu.R import ani.dantotsu.checkGenreTime @@ -266,12 +265,33 @@ class AnilistQueries { suspend fun continueMedia(type: String, planned: Boolean = false): ArrayList { val returnArray = arrayListOf() val map = mutableMapOf() - val statuses = if (!planned) arrayOf("CURRENT", "REPEATING") else arrayOf("PLANNING") - suspend fun repeat(status: String) { - val response = - executeQuery(""" { MediaListCollection(userId: ${Anilist.userid}, type: $type, status: $status , sort: UPDATED_TIME ) { lists { entries { progress private score(format:POINT_100) status media { id idMal type isAdult status chapters episodes nextAiringEpisode {episode} meanScore isFavourite format bannerImage coverImage{large} title { english romaji userPreferred } } } } } } """) + val query = if (planned) { + """{ planned: ${continueMediaQuery(type, "PLANNING")} }""" + } else { + """{ + current: ${continueMediaQuery(type, "CURRENT")}, + repeating: ${continueMediaQuery(type, "REPEATING")} + }""" + } - response?.data?.mediaListCollection?.lists?.forEach { li -> + val response = executeQuery(query) + if (planned) { + response?.data?.planned?.lists?.forEach { li -> + li.entries?.reversed()?.forEach { + val m = Media(it) + m.cameFromContinue = true + map[m.id] = m + } + } + } else { + response?.data?.current?.lists?.forEach { li -> + li.entries?.reversed()?.forEach { + val m = Media(it) + m.cameFromContinue = true + map[m.id] = m + } + } + response?.data?.repeating?.lists?.forEach { li -> li.entries?.reversed()?.forEach { val m = Media(it) m.cameFromContinue = true @@ -279,8 +299,6 @@ class AnilistQueries { } } } - - statuses.forEach { repeat(it) } val set = PrefManager.getCustomVal>("continue_$type", setOf()).toMutableSet() if (set.isNotEmpty()) { set.reversed().forEach { @@ -293,13 +311,16 @@ class AnilistQueries { return returnArray } + private fun continueMediaQuery(type: String, status: String): String { + return """ MediaListCollection(userId: ${Anilist.userid}, type: $type, status: $status , sort: UPDATED_TIME ) { lists { entries { progress private score(format:POINT_100) status media { id idMal type isAdult status chapters episodes nextAiringEpisode {episode} meanScore isFavourite format bannerImage coverImage{large} title { english romaji userPreferred } } } } } """ + } + suspend fun favMedia(anime: Boolean): ArrayList { var hasNextPage = true var page = 0 suspend fun getNextPage(page: Int): List { - val response = - executeQuery("""{User(id:${Anilist.userid}){id favourites{${if (anime) "anime" else "manga"}(page:$page){pageInfo{hasNextPage}edges{favouriteOrder node{id idMal isAdult mediaListEntry{ progress private score(format:POINT_100) status } chapters isFavourite format episodes nextAiringEpisode{episode}meanScore isFavourite format startDate{year month day} title{english romaji userPreferred}type status(version:2)bannerImage coverImage{large}}}}}}}""") + val response = executeQuery("""{${favMediaQuery(anime, page)}}""") val favourites = response?.data?.user?.favourites val apiMediaList = if (anime) favourites?.anime else favourites?.manga hasNextPage = apiMediaList?.pageInfo?.hasNextPage ?: false @@ -318,9 +339,12 @@ class AnilistQueries { return responseArray } + private fun favMediaQuery(anime: Boolean, page: Int): String { + return """User(id:${Anilist.userid}){id favourites{${if (anime) "anime" else "manga"}(page:$page){pageInfo{hasNextPage}edges{favouriteOrder node{id idMal isAdult mediaListEntry{ progress private score(format:POINT_100) status } chapters isFavourite format episodes nextAiringEpisode{episode}meanScore isFavourite format startDate{year month day} title{english romaji userPreferred}type status(version:2)bannerImage coverImage{large}}}}}}""" + } + suspend fun recommendations(): ArrayList { - val response = - executeQuery(""" { Page(page: 1, perPage:30) { pageInfo { total currentPage hasNextPage } recommendations(sort: RATING_DESC, onList: true) { rating userRating mediaRecommendation { id idMal isAdult mediaListEntry { progress private score(format:POINT_100) status } chapters isFavourite format episodes nextAiringEpisode {episode} popularity meanScore isFavourite format title {english romaji userPreferred } type status(version: 2) bannerImage coverImage { large } } } } } """) + val response = executeQuery("""{${recommendationQuery()}}""") val map = mutableMapOf() response?.data?.page?.apply { recommendations?.onEach { @@ -336,7 +360,7 @@ class AnilistQueries { val types = arrayOf("ANIME", "MANGA") suspend fun repeat(type: String) { val res = - executeQuery(""" { MediaListCollection(userId: ${Anilist.userid}, type: $type, status: PLANNING , sort: MEDIA_POPULARITY_DESC ) { lists { entries { media { id mediaListEntry { progress private score(format:POINT_100) status } idMal type isAdult popularity status(version: 2) chapters episodes nextAiringEpisode {episode} meanScore isFavourite format bannerImage coverImage{large} title { english romaji userPreferred } } } } } } """) + executeQuery("""{${recommendationPlannedQuery(type)}}""") res?.data?.mediaListCollection?.lists?.forEach { li -> li.entries?.forEach { val m = Media(it) @@ -354,6 +378,185 @@ class AnilistQueries { return list } + private fun recommendationQuery(): String { + return """ Page(page: 1, perPage:30) { pageInfo { total currentPage hasNextPage } recommendations(sort: RATING_DESC, onList: true) { rating userRating mediaRecommendation { id idMal isAdult mediaListEntry { progress private score(format:POINT_100) status } chapters isFavourite format episodes nextAiringEpisode {episode} popularity meanScore isFavourite format title {english romaji userPreferred } type status(version: 2) bannerImage coverImage { large } } } } """ + } + + private fun recommendationPlannedQuery(type: String): String { + return """ MediaListCollection(userId: ${Anilist.userid}, type: $type, status: PLANNING , sort: MEDIA_POPULARITY_DESC ) { lists { entries { media { id mediaListEntry { progress private score(format:POINT_100) status } idMal type isAdult popularity status(version: 2) chapters episodes nextAiringEpisode {episode} meanScore isFavourite format bannerImage coverImage{large} title { english romaji userPreferred } } } } }""" + } + + suspend fun initHomePage(): Map> { + val toShow: List = + PrefManager.getVal(PrefName.HomeLayoutShow) // anime continue, anime fav, anime planned, manga continue, manga fav, manga planned, recommendations + var query = """{""" + if (toShow.getOrNull(0) == true) query += """currentAnime: ${ + continueMediaQuery( + "ANIME", + "CURRENT" + ) + }, repeatingAnime: ${continueMediaQuery("ANIME", "REPEATING")}""" + if (toShow.getOrNull(1) == true) query += """favoriteAnime: ${favMediaQuery(true, 1)}""" + if (toShow.getOrNull(2) == true) query += """plannedAnime: ${ + continueMediaQuery( + "ANIME", + "PLANNING" + ) + }""" + if (toShow.getOrNull(3) == true) query += """currentManga: ${ + continueMediaQuery( + "MANGA", + "CURRENT" + ) + }, repeatingManga: ${continueMediaQuery("MANGA", "REPEATING")}""" + if (toShow.getOrNull(4) == true) query += """favoriteManga: ${favMediaQuery(false, 1)}""" + if (toShow.getOrNull(5) == true) query += """plannedManga: ${ + continueMediaQuery( + "MANGA", + "PLANNING" + ) + }""" + if (toShow.getOrNull(6) == true) query += """recommendationQuery: ${recommendationQuery()}, recommendationPlannedQueryAnime: ${ + recommendationPlannedQuery( + "ANIME" + ) + }, recommendationPlannedQueryManga: ${recommendationPlannedQuery("MANGA")}""" + query += """}""".trimEnd(',') + + val response = executeQuery(query) + val returnMap = mutableMapOf>() + fun current(type: String) { + val subMap = mutableMapOf() + val returnArray = arrayListOf() + val current = + if (type == "Anime") response?.data?.currentAnime else response?.data?.currentManga + val repeating = + if (type == "Anime") response?.data?.repeatingAnime else response?.data?.repeatingManga + current?.lists?.forEach { li -> + li.entries?.reversed()?.forEach { + val m = Media(it) + m.cameFromContinue = true + subMap[m.id] = m + } + } + repeating?.lists?.forEach { li -> + li.entries?.reversed()?.forEach { + val m = Media(it) + m.cameFromContinue = true + subMap[m.id] = m + } + } + val set = PrefManager.getCustomVal>("continue_${type.uppercase()}", setOf()) + .toMutableSet() + if (set.isNotEmpty()) { + set.reversed().forEach { + if (subMap.containsKey(it)) returnArray.add(subMap[it]!!) + } + for (i in subMap) { + if (i.value !in returnArray) returnArray.add(i.value) + } + } else returnArray.addAll(subMap.values) + returnMap["current$type"] = returnArray + + } + + fun planned(type: String) { + val subMap = mutableMapOf() + val returnArray = arrayListOf() + val current = + if (type == "Anime") response?.data?.plannedAnime else response?.data?.plannedManga + current?.lists?.forEach { li -> + li.entries?.reversed()?.forEach { + val m = Media(it) + m.cameFromContinue = true + subMap[m.id] = m + } + } + val set = PrefManager.getCustomVal>("continue_$type", setOf()).toMutableSet() + if (set.isNotEmpty()) { + set.reversed().forEach { + if (subMap.containsKey(it)) returnArray.add(subMap[it]!!) + } + for (i in subMap) { + if (i.value !in returnArray) returnArray.add(i.value) + } + } else returnArray.addAll(subMap.values) + returnMap["planned$type"] = returnArray + } + + fun favorite(type: String) { + val favourites = + if (type == "Anime") response?.data?.favoriteAnime?.favourites else response?.data?.favoriteManga?.favourites + val apiMediaList = if (type == "Anime") favourites?.anime else favourites?.manga + val returnArray = arrayListOf() + apiMediaList?.edges?.forEach { + it.node?.let { i -> + returnArray.add(Media(i).apply { isFav = true }) + } + } + returnMap["favorite$type"] = returnArray + } + + if (toShow.getOrNull(0) == true) { + current("Anime") + } + if (toShow.getOrNull(1) == true) { + favorite("Anime") + } + if (toShow.getOrNull(2) == true) { + planned("Anime") + } + if (toShow.getOrNull(3) == true) { + current("Manga") + } + if (toShow.getOrNull(4) == true) { + favorite("Manga") + } + if (toShow.getOrNull(5) == true) { + planned("Manga") + } + if (toShow.getOrNull(6) == true) { + val subMap = mutableMapOf() + response?.data?.recommendationQuery?.apply { + recommendations?.onEach { + val json = it.mediaRecommendation + if (json != null) { + val m = Media(json) + m.relation = json.type?.toString() + subMap[m.id] = m + } + } + } + response?.data?.recommendationPlannedQueryAnime?.apply { + lists?.forEach { li -> + li.entries?.forEach { + val m = Media(it) + if (m.status == "RELEASING" || m.status == "FINISHED") { + m.relation = it.media?.type?.toString() + subMap[m.id] = m + } + } + } + } + response?.data?.recommendationPlannedQueryManga?.apply { + lists?.forEach { li -> + li.entries?.forEach { + val m = Media(it) + if (m.status == "RELEASING" || m.status == "FINISHED") { + m.relation = it.media?.type?.toString() + subMap[m.id] = m + } + } + } + } + val list = ArrayList(subMap.values.toList()) + list.sortByDescending { it.meanScore } + returnMap["recommendations"] = list + } + return returnMap + } + + private suspend fun bannerImage(type: String): String? { //var image = loadData("banner_$type") val image: BannerImage? = BannerImage( diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistViewModel.kt b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistViewModel.kt index 5e3bacb9..2709c717 100644 --- a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistViewModel.kt +++ b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistViewModel.kt @@ -96,6 +96,17 @@ class AnilistHomeViewModel : ViewModel() { fun getRecommendation(): LiveData> = recommendation suspend fun setRecommendation() = recommendation.postValue(Anilist.query.recommendations()) + + suspend fun initHomePage() { + val res = Anilist.query.initHomePage() + res["currentAnime"]?.let { animeContinue.postValue(it) } + res["favoriteAnime"]?.let { animeFav.postValue(it) } + res["plannedAnime"]?.let { animePlanned.postValue(it) } + res["currentManga"]?.let { mangaContinue.postValue(it) } + res["favoriteManga"]?.let { mangaFav.postValue(it) } + res["plannedManga"]?.let { mangaPlanned.postValue(it) } + res["recommendations"]?.let { recommendation.postValue(it) } + } suspend fun loadMain(context: FragmentActivity) { Anilist.getSavedToken() diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/api/Data.kt b/app/src/main/java/ani/dantotsu/connections/anilist/api/Data.kt index 5b3a16ea..8e53de02 100644 --- a/app/src/main/java/ani/dantotsu/connections/anilist/api/Data.kt +++ b/app/src/main/java/ani/dantotsu/connections/anilist/api/Data.kt @@ -105,6 +105,40 @@ class Query { ) } + @Serializable + data class CombinedMediaListResponse( + @SerialName("data") + val data: Data? + ) { + @Serializable + data class Data( + @SerialName("current") val current: ani.dantotsu.connections.anilist.api.MediaListCollection?, + @SerialName("planned") val planned: ani.dantotsu.connections.anilist.api.MediaListCollection?, + @SerialName("repeating") val repeating: ani.dantotsu.connections.anilist.api.MediaListCollection?, + ) + } + + @Serializable + data class HomePageMedia( + @SerialName("data") + val data: Data? + ) { + @Serializable + data class Data( + @SerialName("currentAnime") val currentAnime: ani.dantotsu.connections.anilist.api.MediaListCollection?, + @SerialName("repeatingAnime") val repeatingAnime: ani.dantotsu.connections.anilist.api.MediaListCollection?, + @SerialName("favoriteAnime") val favoriteAnime: ani.dantotsu.connections.anilist.api.User?, + @SerialName("plannedAnime") val plannedAnime: ani.dantotsu.connections.anilist.api.MediaListCollection?, + @SerialName("currentManga") val currentManga: ani.dantotsu.connections.anilist.api.MediaListCollection?, + @SerialName("repeatingManga") val repeatingManga: ani.dantotsu.connections.anilist.api.MediaListCollection?, + @SerialName("favoriteManga") val favoriteManga: ani.dantotsu.connections.anilist.api.User?, + @SerialName("plannedManga") val plannedManga: ani.dantotsu.connections.anilist.api.MediaListCollection?, + @SerialName("recommendationQuery") val recommendationQuery: ani.dantotsu.connections.anilist.api.Page?, + @SerialName("recommendationPlannedQueryAnime") val recommendationPlannedQueryAnime: ani.dantotsu.connections.anilist.api.MediaListCollection?, + @SerialName("recommendationPlannedQueryManga") val recommendationPlannedQueryManga: ani.dantotsu.connections.anilist.api.MediaListCollection?, + ) + } + @Serializable data class GenreCollection( @SerialName("data") diff --git a/app/src/main/java/ani/dantotsu/home/HomeFragment.kt b/app/src/main/java/ani/dantotsu/home/HomeFragment.kt index 2792a832..a1a94763 100644 --- a/app/src/main/java/ani/dantotsu/home/HomeFragment.kt +++ b/app/src/main/java/ani/dantotsu/home/HomeFragment.kt @@ -305,7 +305,7 @@ class HomeFragment : Fragment() { } } - val array = arrayOf( + val array = arrayOf( //deprecated: no longer should be run. only indices are used Runnable { runBlocking { model.setAnimeContinue() } }, Runnable { runBlocking { model.setAnimeFav() } }, Runnable { runBlocking { model.setAnimePlanned() } }, @@ -339,9 +339,11 @@ class HomeFragment : Fragment() { var empty = true val homeLayoutShow: List = PrefManager.getVal(PrefName.HomeLayoutShow) + runBlocking { + model.initHomePage() + } (array.indices).forEach { i -> if (homeLayoutShow.elementAt(i)) { - array[i].run() empty = false } else withContext(Dispatchers.Main) { containers[i].visibility = View.GONE