-
Notifications
You must be signed in to change notification settings - Fork 41
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' of https://github.com/almightyhak/aniyomi-extensions
- Loading branch information
Showing
4 changed files
with
302 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
plugins { | ||
id("lib-multisrc") | ||
} | ||
|
||
baseVersionCode = 1 |
167 changes: 167 additions & 0 deletions
167
lib-multisrc/anilist/src/eu/kanade/tachiyomi/multisrc/anilist/AniListAnimeHttpSource.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
package eu.kanade.tachiyomi.multisrc.anilist | ||
|
||
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList | ||
import eu.kanade.tachiyomi.animesource.model.AnimesPage | ||
import eu.kanade.tachiyomi.animesource.model.SAnime | ||
import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource | ||
import eu.kanade.tachiyomi.network.POST | ||
import eu.kanade.tachiyomi.util.parseAs | ||
import kotlinx.serialization.encodeToString | ||
import kotlinx.serialization.json.Json | ||
import okhttp3.FormBody | ||
import okhttp3.Request | ||
import okhttp3.Response | ||
import uy.kohesive.injekt.injectLazy | ||
|
||
abstract class AniListAnimeHttpSource : AnimeHttpSource() { | ||
override val supportsLatest = true | ||
val json by injectLazy<Json>() | ||
|
||
/* =============================== Mapping AniList <> Source =============================== */ | ||
abstract fun mapAnimeDetailUrl(animeId: Int): String | ||
|
||
abstract fun mapAnimeId(animeDetailUrl: String): Int | ||
|
||
open fun getPreferredTitleLanguage(): TitleLanguage { | ||
return TitleLanguage.ROMAJI | ||
} | ||
|
||
/* ===================================== Popular Anime ===================================== */ | ||
override fun popularAnimeRequest(page: Int): Request { | ||
return buildAnimeListRequest( | ||
query = ANIME_LIST_QUERY, | ||
variables = AnimeListVariables( | ||
page = page, | ||
sort = AnimeListVariables.MediaSort.POPULARITY_DESC, | ||
), | ||
) | ||
} | ||
|
||
override fun popularAnimeParse(response: Response): AnimesPage { | ||
return parseAnimeListResponse(response) | ||
} | ||
|
||
/* ===================================== Latest Anime ===================================== */ | ||
override fun latestUpdatesRequest(page: Int): Request { | ||
return buildAnimeListRequest( | ||
query = LATEST_ANIME_LIST_QUERY, | ||
variables = AnimeListVariables( | ||
page = page, | ||
sort = AnimeListVariables.MediaSort.START_DATE_DESC, | ||
), | ||
) | ||
} | ||
|
||
override fun latestUpdatesParse(response: Response): AnimesPage { | ||
return parseAnimeListResponse(response) | ||
} | ||
|
||
/* ===================================== Search Anime ===================================== */ | ||
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request { | ||
return buildAnimeListRequest( | ||
query = ANIME_LIST_QUERY, | ||
variables = AnimeListVariables( | ||
page = page, | ||
sort = AnimeListVariables.MediaSort.SEARCH_MATCH, | ||
search = query.ifBlank { null }, | ||
), | ||
) | ||
} | ||
|
||
override fun searchAnimeParse(response: Response): AnimesPage { | ||
return parseAnimeListResponse(response) | ||
} | ||
|
||
/* ===================================== Anime Details ===================================== */ | ||
override fun animeDetailsRequest(anime: SAnime): Request { | ||
return buildRequest( | ||
query = ANIME_DETAILS_QUERY, | ||
variables = json.encodeToString(AnimeDetailsVariables(mapAnimeId(anime.url))), | ||
) | ||
} | ||
|
||
override fun animeDetailsParse(response: Response): SAnime { | ||
val media = response.parseAs<AniListAnimeDetailsResponse>().data.media | ||
|
||
return media.toSAnime() | ||
} | ||
|
||
override fun getAnimeUrl(anime: SAnime): String { | ||
return anime.url | ||
} | ||
|
||
/* ==================================== AniList Utility ==================================== */ | ||
private fun buildAnimeListRequest( | ||
query: String, | ||
variables: AnimeListVariables, | ||
): Request { | ||
return buildRequest(query, json.encodeToString(variables)) | ||
} | ||
|
||
private fun buildRequest(query: String, variables: String): Request { | ||
val requestBody = FormBody.Builder() | ||
.add("query", query) | ||
.add("variables", variables) | ||
.build() | ||
|
||
return POST(url = "https://graphql.anilist.co", body = requestBody) | ||
} | ||
|
||
private fun parseAnimeListResponse(response: Response): AnimesPage { | ||
val page = response.parseAs<AniListAnimeListResponse>().data.page | ||
|
||
return AnimesPage( | ||
animes = page.media.map { it.toSAnime() }, | ||
hasNextPage = page.pageInfo.hasNextPage, | ||
) | ||
} | ||
|
||
private fun AniListMedia.toSAnime(): SAnime { | ||
val otherNames = when (getPreferredTitleLanguage()) { | ||
TitleLanguage.ROMAJI -> listOfNotNull(title.english, title.native) | ||
TitleLanguage.ENGLISH -> listOfNotNull(title.romaji, title.native) | ||
TitleLanguage.NATIVE -> listOfNotNull(title.romaji, title.english) | ||
} | ||
val newDescription = buildString { | ||
append( | ||
description | ||
?.replace("<br>\n<br>", "\n") | ||
?.replace("<.*?>".toRegex(), ""), | ||
) | ||
if (otherNames.isNotEmpty()) { | ||
appendLine() | ||
appendLine() | ||
append("Other name(s): ${otherNames.joinToString(", ")}") | ||
} | ||
} | ||
val media = this | ||
|
||
return SAnime.create().apply { | ||
url = mapAnimeDetailUrl(media.id) | ||
title = parseTitle(media.title) | ||
author = media.studios.nodes.joinToString(", ") { it.name } | ||
description = newDescription | ||
genre = media.genres.joinToString(", ") | ||
status = when (media.status) { | ||
AniListMedia.Status.RELEASING -> SAnime.ONGOING | ||
AniListMedia.Status.FINISHED -> SAnime.COMPLETED | ||
} | ||
thumbnail_url = media.coverImage.large | ||
initialized = true | ||
} | ||
} | ||
|
||
private fun parseTitle(title: AniListMedia.Title): String { | ||
return when (getPreferredTitleLanguage()) { | ||
TitleLanguage.ROMAJI -> title.romaji | ||
TitleLanguage.ENGLISH -> title.english ?: title.romaji | ||
TitleLanguage.NATIVE -> title.native ?: title.romaji | ||
} | ||
} | ||
|
||
enum class TitleLanguage { | ||
ROMAJI, | ||
ENGLISH, | ||
NATIVE, | ||
} | ||
} |
73 changes: 73 additions & 0 deletions
73
lib-multisrc/anilist/src/eu/kanade/tachiyomi/multisrc/anilist/AniListRequest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package eu.kanade.tachiyomi.multisrc.anilist | ||
|
||
import kotlinx.serialization.Serializable | ||
|
||
internal const val MEDIA_QUERY = """ | ||
id | ||
title { | ||
romaji | ||
english | ||
native | ||
} | ||
coverImage { | ||
large | ||
} | ||
description | ||
status | ||
genres | ||
studios(isMain: true) { | ||
nodes { | ||
name | ||
} | ||
} | ||
""" | ||
|
||
internal const val ANIME_LIST_QUERY = """ | ||
query (${"$"}page: Int, ${"$"}sort: [MediaSort], ${"$"}search: String) { | ||
Page(page: ${"$"}page, perPage: 30) { | ||
pageInfo { | ||
hasNextPage | ||
} | ||
media(type: ANIME, sort: ${"$"}sort, search: ${"$"}search, status_in: [RELEASING, FINISHED], countryOfOrigin: "JP", isAdult: false) { | ||
$MEDIA_QUERY | ||
} | ||
} | ||
} | ||
""" | ||
|
||
internal const val LATEST_ANIME_LIST_QUERY = """ | ||
query (${"$"}page: Int, ${"$"}sort: [MediaSort], ${"$"}search: String) { | ||
Page(page: ${"$"}page, perPage: 30) { | ||
pageInfo { | ||
hasNextPage | ||
} | ||
media(type: ANIME, sort: ${"$"}sort, search: ${"$"}search, status_in: [RELEASING, FINISHED], countryOfOrigin: "JP", isAdult: false, startDate_greater: 1, episodes_greater: 1) { | ||
$MEDIA_QUERY | ||
} | ||
} | ||
} | ||
""" | ||
|
||
internal const val ANIME_DETAILS_QUERY = """ | ||
query (${"$"}id: Int) { | ||
Media(id: ${"$"}id) { | ||
$MEDIA_QUERY | ||
} | ||
} | ||
""" | ||
|
||
@Serializable | ||
internal data class AnimeListVariables( | ||
val page: Int, | ||
val sort: MediaSort, | ||
val search: String? = null, | ||
) { | ||
enum class MediaSort { | ||
POPULARITY_DESC, | ||
SEARCH_MATCH, | ||
START_DATE_DESC, | ||
} | ||
} | ||
|
||
@Serializable | ||
internal data class AnimeDetailsVariables(val id: Int) |
57 changes: 57 additions & 0 deletions
57
lib-multisrc/anilist/src/eu/kanade/tachiyomi/multisrc/anilist/AniListResponse.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package eu.kanade.tachiyomi.multisrc.anilist | ||
|
||
import kotlinx.serialization.SerialName | ||
import kotlinx.serialization.Serializable | ||
|
||
@Serializable | ||
internal data class AniListAnimeListResponse(val data: Data) { | ||
@Serializable | ||
data class Data(@SerialName("Page") val page: Page) { | ||
@Serializable | ||
data class Page( | ||
val pageInfo: PageInfo, | ||
val media: List<AniListMedia>, | ||
) { | ||
@Serializable | ||
data class PageInfo(val hasNextPage: Boolean) | ||
} | ||
} | ||
} | ||
|
||
@Serializable | ||
internal data class AniListAnimeDetailsResponse(val data: Data) { | ||
@Serializable | ||
data class Data(@SerialName("Media") val media: AniListMedia) | ||
} | ||
|
||
@Serializable | ||
internal data class AniListMedia( | ||
val id: Int, | ||
val title: Title, | ||
val coverImage: CoverImage, | ||
val description: String?, | ||
val status: Status, | ||
val genres: List<String>, | ||
val studios: Studios, | ||
) { | ||
@Serializable | ||
data class Title( | ||
val romaji: String, | ||
val english: String?, | ||
val native: String?, | ||
) | ||
|
||
@Serializable | ||
data class CoverImage(val large: String) | ||
|
||
enum class Status { | ||
RELEASING, | ||
FINISHED, | ||
} | ||
|
||
@Serializable | ||
data class Studios(val nodes: List<Node>) { | ||
@Serializable | ||
data class Node(val name: String) | ||
} | ||
} |