AllAnime refactor (#16660)

* AllAnime refactor

* code cleanup
* chapter titles\dates
* image quality setting

* [skip ci] add more warning
This commit is contained in:
AwkwardPeak7 2023-06-06 14:19:32 +05:00 committed by GitHub
parent 1b47daff10
commit 6aa5ac0f17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 493 additions and 240 deletions

View File

@ -6,7 +6,7 @@ ext {
extName = 'AllAnime'
pkgNameSuffix = 'en.allanime'
extClass = '.AllAnime'
extVersionCode = 2
extVersionCode = 3
}
apply from: "$rootDir/common.gradle"

View File

@ -10,8 +10,6 @@ import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.network.interceptor.rateLimitHost
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.model.Filter.TriState.Companion.STATE_EXCLUDE
import eu.kanade.tachiyomi.source.model.Filter.TriState.Companion.STATE_INCLUDE
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
@ -20,24 +18,18 @@ import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import kotlinx.serialization.json.putJsonObject
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import org.jsoup.Jsoup
import rx.Observable
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
import java.util.Locale
class AllAnime : ConfigurableSource, HttpSource() {
@ -48,18 +40,41 @@ class AllAnime : ConfigurableSource, HttpSource() {
override val supportsLatest = true
private val json: Json by injectLazy()
private val json: Json = Json {
ignoreUnknownKeys = true
explicitNulls = false
encodeDefaults = true
coerceInputValues = true
}
private val preferences: SharedPreferences =
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
private val domain = preferences.getString(DOMAIN_PREF, "allanime.to")
private val domain = preferences.domainPref
override val baseUrl = "https://$domain"
private val apiUrl = "https://api.$domain/allanimeapi"
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.addInterceptor { chain ->
val request = chain.request()
val frag = request.url.fragment
val quality = preferences.imageQuality
if (frag.isNullOrEmpty() || quality == IMAGE_QUALITY_PREF_DEFAULT) {
return@addInterceptor chain.proceed(request)
}
val oldUrl = request.url.toString()
val newUrl = oldUrl.replace(imageQualityRegex, "$image_cdn/$1?w=$quality")
return@addInterceptor chain.proceed(
request.newBuilder()
.url(newUrl)
.build(),
)
}
.rateLimitHost(apiUrl.toHttpUrl(), 1)
.build()
@ -68,31 +83,22 @@ class AllAnime : ConfigurableSource, HttpSource() {
/* Popular */
override fun popularMangaRequest(page: Int): Request {
val showAdult = preferences.getBoolean(SHOW_ADULT_PREF, false)
val payloadObj = ApiPopularPayload(
size = limit,
dateRange = 0,
page = page,
allowAdult = preferences.allowAdult,
)
val payload = buildJsonObject {
putJsonObject("variables") {
put("type", "manga")
put("size", limit)
put("dateRange", 0)
put("page", page)
put("allowAdult", showAdult)
put("allowUnknown", false)
}
put("query", POPULAR_QUERY)
}
return apiRequest(payload)
return apiRequest(payloadObj)
}
override fun popularMangaParse(response: Response): MangasPage {
val result = json.decodeFromString<ApiPopularResponse>(response.body.string())
val result = response.parseAs<ApiPopularResponse>()
val titleStyle = preferences.titlePref
val mangaList = result.data.queryPopular.recommendations
.mapNotNull { it.anyCard }
.map { manga ->
toSManga(manga)
}
val mangaList = result.data.popular.mangas
.mapNotNull { it.manga?.toSManga(titleStyle) }
return MangasPage(mangaList, mangaList.size == limit)
}
@ -115,58 +121,26 @@ class AllAnime : ConfigurableSource, HttpSource() {
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val showAdult = preferences.getBoolean(SHOW_ADULT_PREF, false)
var country = "ALL"
val includeGenres = mutableListOf<String>()
val excludeGenres = mutableListOf<String>()
val payloadObj = ApiSearchPayload(
query = query,
size = limit,
page = page,
genres = filters.firstInstanceOrNull<GenreFilter>()?.included,
excludeGenres = filters.firstInstanceOrNull<GenreFilter>()?.excluded,
translationType = "sub",
countryOrigin = filters.firstInstanceOrNull<CountryFilter>()?.getValue() ?: "ALL",
allowAdult = preferences.allowAdult,
)
filters.forEach { filter ->
when (filter) {
is GenreFilter -> {
filter.state.forEach { genreState ->
when (genreState.state) {
STATE_INCLUDE -> includeGenres.add(genreState.name)
STATE_EXCLUDE -> excludeGenres.add(genreState.name)
}
}
}
is CountryFilter -> {
country = filter.getValue()
}
else -> {}
}
}
val payload = buildJsonObject {
putJsonObject("variables") {
putJsonObject("search") {
if (includeGenres.isNotEmpty() || excludeGenres.isNotEmpty()) {
put("genres", JsonArray(includeGenres.map { JsonPrimitive(it) }))
put("excludeGenres", JsonArray(excludeGenres.map { JsonPrimitive(it) }))
}
if (query.isNotEmpty()) put("query", query)
put("allowAdult", showAdult)
put("allowUnknown", false)
put("isManga", true)
}
put("limit", limit)
put("page", page)
put("translationType", "sub")
put("countryOrigin", country)
}
put("query", SEARCH_QUERY)
}
return apiRequest(payload)
return apiRequest(payloadObj)
}
override fun searchMangaParse(response: Response): MangasPage {
val result = json.decodeFromString<ApiSearchResponse>(response.body.string())
val result = response.parseAs<ApiSearchResponse>()
val titleStyle = preferences.titlePref
val mangaList = result.data.mangas.edges
.map { manga ->
toSManga(manga)
}
val mangaList = result.data.mangas.mangas
.map { it.toSManga(titleStyle) }
return MangasPage(mangaList, mangaList.size == limit)
}
@ -176,22 +150,15 @@ class AllAnime : ConfigurableSource, HttpSource() {
/* Details */
override fun mangaDetailsRequest(manga: SManga): Request {
val mangaId = manga.url.split("/")[2]
val payloadObj = ApiIDPayload(mangaId, DETAILS_QUERY)
val payload = buildJsonObject {
putJsonObject("variables") {
put("_id", mangaId)
}
put("query", DETAILS_QUERY)
}
return apiRequest(payload)
return apiRequest(payloadObj)
}
override fun mangaDetailsParse(response: Response): SManga {
val result = json.decodeFromString<ApiMangaDetailsResponse>(response.body.string())
val manga = result.data.manga
val result = response.parseAs<ApiMangaDetailsResponse>()
return toSManga(manga)
return result.data.manga.toSManga(preferences.titlePref)
}
override fun getMangaUrl(manga: SManga): String {
@ -209,30 +176,44 @@ class AllAnime : ConfigurableSource, HttpSource() {
override fun chapterListRequest(manga: SManga): Request {
val mangaId = manga.url.split("/")[2]
val payloadObj = ApiIDPayload(mangaId, CHAPTERS_QUERY)
val payload = buildJsonObject {
putJsonObject("variables") {
put("_id", mangaId)
}
put("query", CHAPTERS_QUERY)
}
return apiRequest(payloadObj)
}
return apiRequest(payload)
private fun chapterDetailsRequest(manga: SManga, start: String, end: String): Request {
val mangaId = manga.url.split("/")[2]
val payloadObj = ApiChapterListDetailsPayload(mangaId, start.toFloat(), end.toFloat())
return apiRequest(payloadObj)
}
private fun chapterListParse(response: Response, manga: SManga): List<SChapter> {
val result = json.decodeFromString<ApiChapterListResponse>(response.body.string())
val result = response.parseAs<ApiChapterListResponse>()
val chapters = result.data.manga.chapters.sub
?.sortedBy { it.toFloat() }
?: return emptyList()
val chapters = result.data.manga.availableChaptersDetail.sub
val chapterDetails = client.newCall(
chapterDetailsRequest(manga, chapters.first(), chapters.last()),
).execute()
.use {
it.parseAs<ApiChapterListDetailsResponse>()
}.data.chapterList
?.sortedBy { it.chapterNum }
val mangaUrl = manga.url.substringAfter("/manga/")
return chapters?.map { chapter ->
return chapterDetails?.zip(chapters)?.map { (details, chapterNum) ->
SChapter.create().apply {
name = "Chapter $chapter"
url = "/read/$mangaUrl/chapter-$chapter-sub"
name = "Chapter $chapterNum"
if (!details.title.isNullOrEmpty() && !details.title.contains(numberRegex)) {
name += ": ${details.title}"
}
url = "/read/$mangaUrl/chapter-$chapterNum-sub"
date_upload = details.uploadDates?.sub.parseDate()
}
} ?: emptyList()
}?.reversed() ?: emptyList()
}
override fun chapterListParse(response: Response): List<SChapter> {
@ -257,24 +238,21 @@ class AllAnime : ConfigurableSource, HttpSource() {
val mangaId = chapterUrl[2]
val chapterNo = chapterUrl[4].split("-")[1]
val payload = buildJsonObject {
putJsonObject("variables") {
put("mangaId", mangaId)
put("translationType", "sub")
put("chapterString", chapterNo)
}
put("query", PAGE_QUERY)
}
val payloadObj = ApiPageListPayload(
id = mangaId,
chapterNum = chapterNo,
translationType = "sub",
)
return apiRequest(payload)
return apiRequest(payloadObj)
}
private fun pageListParse(response: Response, chapter: SChapter): List<Page> {
val result = json.decodeFromString<ApiPageListResponse>(response.body.string())
val pages = result.data.chapterPages?.edges?.get(0) ?: return emptyList()
val pages = result.data.pageList?.serverList?.get(0) ?: return emptyList()
val imageDomain = if (!pages.pictureUrlHead.isNullOrEmpty()) {
pages.pictureUrlHead.let { server ->
val imageDomain = if (!pages.serverUrl.isNullOrEmpty()) {
pages.serverUrl.let { server ->
if (server.matches(urlRegex)) {
server
} else {
@ -301,7 +279,7 @@ class AllAnime : ConfigurableSource, HttpSource() {
return pages.pictureUrls.mapIndexed { index, image ->
Page(
index = index,
imageUrl = "$imageDomain${image.url}",
imageUrl = "$imageDomain${image.url}#page",
)
}
}
@ -315,106 +293,99 @@ class AllAnime : ConfigurableSource, HttpSource() {
}
/* Helpers */
private fun apiRequest(payload: JsonObject): Request {
val body = payload.toString().toRequestBody(JSON_MEDIA_TYPE)
private inline fun <reified T> apiRequest(payloadObj: T): Request {
val payload = json.encodeToString(payloadObj)
.toRequestBody(JSON_MEDIA_TYPE)
val newHeaders = headersBuilder()
.add("Content-Length", body.contentLength().toString())
.add("Content-Type", body.contentType().toString())
.add("Content-Length", payload.contentLength().toString())
.add("Content-Type", payload.contentType().toString())
.build()
return POST(apiUrl, newHeaders, body)
return POST(apiUrl, newHeaders, payload)
}
private fun toSManga(manga: Manga): SManga {
val titleStyle = preferences.getString(TITLE_PREF, "romaji")!!
private inline fun <reified T> Response.parseAs(): T = json.decodeFromString(body.string())
return SManga.create().apply {
title = when (titleStyle) {
"romaji" -> manga.name
"eng" -> manga.englishName ?: manga.name
else -> manga.nativeName ?: manga.name
}
url = "/manga/${manga._id}/${manga.name.titleToSlug()}"
thumbnail_url = manga.thumbnail.parseThumbnailUrl()
description = Jsoup.parse(
manga.description?.replace("<br>", "br2n") ?: "",
).text().replace("br2n", "\n")
description += if (manga.altNames != null) {
"\n\nAlternative Names: ${manga.altNames.joinToString { it.trim() }}"
} else {
""
}
if (manga.authors?.isNotEmpty() == true) {
author = manga.authors.first().trim()
artist = author
}
genre = "${manga.genres?.joinToString { it.trim() }}, ${manga.tags?.joinToString { it.trim() }}"
status = manga.status.parseStatus()
}
private inline fun <reified R> List<*>.firstInstanceOrNull(): R? =
filterIsInstance<R>().firstOrNull()
private fun String?.parseDate(): Long {
return runCatching {
dateFormat.parse(this!!)!!.time
}.getOrDefault(0L)
}
private fun String.parseThumbnailUrl(): String {
return if (this.matches(urlRegex)) {
this
} else {
"$image_cdn$this?w=250"
}
}
private fun String?.parseStatus(): Int {
if (this == null) {
return SManga.UNKNOWN
}
return when {
this.contains("releasing", true) -> SManga.ONGOING
this.contains("finished", true) -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
}
private fun String.titleToSlug() = this.trim()
.lowercase(Locale.US)
.replace(titleSpecialCharactersRegex, "-")
override fun setupPreferenceScreen(screen: PreferenceScreen) {
ListPreference(screen.context).apply {
key = DOMAIN_PREF
title = "Preferred domain"
entries = arrayOf("allanime.to", "allanime.co")
entryValues = arrayOf("allanime.to", "allanime.co")
setDefaultValue("allanime.to")
setDefaultValue(DOMAIN_PREF_DEFAULT)
summary = "Requires App Restart"
}.let { screen.addPreference(it) }
ListPreference(screen.context).apply {
key = IMAGE_QUALITY_PREF
title = "Image Quality"
entries = arrayOf("Original", "Wp-800", "Wp-480")
entryValues = arrayOf("original", "800", "480")
setDefaultValue(IMAGE_QUALITY_PREF_DEFAULT)
summary = "Warning: Wp quality servers can be slow and might not work sometimes"
}.let { screen.addPreference(it) }
ListPreference(screen.context).apply {
key = TITLE_PREF
title = "Preferred Title Style"
entries = arrayOf("Romaji", "English", "Native")
entryValues = arrayOf("romaji", "eng", "native")
setDefaultValue("romaji")
setDefaultValue(TITLE_PREF_DEFAULT)
summary = "%s"
}.let { screen.addPreference(it) }
SwitchPreferenceCompat(screen.context).apply {
key = SHOW_ADULT_PREF
title = "Show Adult Content"
setDefaultValue(false)
setDefaultValue(SHOW_ADULT_PREF_DEFAULT)
}.let { screen.addPreference(it) }
}
private val SharedPreferences.domainPref
get() = getString(DOMAIN_PREF, DOMAIN_PREF_DEFAULT)!!
private val SharedPreferences.titlePref
get() = getString(TITLE_PREF, TITLE_PREF_DEFAULT)
private val SharedPreferences.allowAdult
get() = getBoolean(SHOW_ADULT_PREF, SHOW_ADULT_PREF_DEFAULT)
private val SharedPreferences.imageQuality
get() = getString(IMAGE_QUALITY_PREF, IMAGE_QUALITY_PREF_DEFAULT)!!
companion object {
private const val limit = 26
private const val limit = 20
private val numberRegex by lazy { Regex("\\d") }
val whitespace by lazy { Regex("\\s+") }
val dateFormat by lazy {
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH)
}
const val SEARCH_PREFIX = "id:"
private const val image_cdn = "https://wp.youtube-anime.com/aln.youtube-anime.com/"
const val thumbnail_cdn = "https://wp.youtube-anime.com/aln.youtube-anime.com/"
private val JSON_MEDIA_TYPE = "application/json; charset=utf-8".toMediaTypeOrNull()
private val urlRegex = Regex("^https?://.*")
private val titleSpecialCharactersRegex = Regex("[^a-z\\d]+")
val urlRegex = Regex("^https?://.*")
private const val image_cdn = "https://wp.youtube-anime.com"
private val imageQualityRegex = Regex("^https?://(.*)#.*")
val titleSpecialCharactersRegex = Regex("[^a-z\\d]+")
private val imageUrlFromPageRegex = Regex("selectedPicturesServer:\\[\\{.*?url:\"(.*?)\".*?\\}\\]")
private const val DOMAIN_PREF = "pref_domain"
private const val DOMAIN_PREF_DEFAULT = "allanime.to"
private const val TITLE_PREF = "pref_title"
private const val TITLE_PREF_DEFAULT = "romaji"
private const val SHOW_ADULT_PREF = "pref_adult"
private const val SHOW_ADULT_PREF_DEFAULT = false
private const val IMAGE_QUALITY_PREF = "pref_quality"
private const val IMAGE_QUALITY_PREF_DEFAULT = "original"
}
}

View File

@ -1,22 +1,26 @@
package eu.kanade.tachiyomi.extension.en.allanime
import eu.kanade.tachiyomi.source.model.SManga
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import org.jsoup.Jsoup
import java.util.Locale
@Serializable
data class ApiPopularResponse(
val data: PopularResultData,
val data: PopularResponseData,
) {
@Serializable
data class PopularResultData(
val queryPopular: QueryPopularData,
data class PopularResponseData(
@SerialName("queryPopular") val popular: PopularData,
) {
@Serializable
data class QueryPopularData(
val recommendations: List<Recommendation>,
data class PopularData(
@SerialName("recommendations") val mangas: List<Popular>,
) {
@Serializable
data class Recommendation(
val anyCard: Manga? = null,
data class Popular(
@SerialName("anyCard") val manga: SearchManga? = null,
)
}
}
@ -24,19 +28,34 @@ data class ApiPopularResponse(
@Serializable
data class ApiSearchResponse(
val data: SearchResultData,
val data: SearchResponseData,
) {
@Serializable
data class SearchResultData(
data class SearchResponseData(
val mangas: SearchResultMangas,
) {
@Serializable
data class SearchResultMangas(
val edges: List<Manga>,
@SerialName("edges") val mangas: List<SearchManga>,
)
}
}
@Serializable
data class SearchManga(
@SerialName("_id") val id: String,
val name: String,
val thumbnail: String? = null,
val englishName: String? = null,
val nativeName: String? = null,
) {
fun toSManga(titleStyle: String?) = SManga.create().apply {
title = titleStyle.preferedName(name, englishName, nativeName)
url = "/manga/$id/${name.titleToSlug()}"
thumbnail_url = thumbnail?.parseThumbnailUrl()
}
}
@Serializable
data class ApiMangaDetailsResponse(
val data: MangaDetailsData,
@ -44,24 +63,43 @@ data class ApiMangaDetailsResponse(
@Serializable
data class MangaDetailsData(
val manga: Manga,
)
) {
@Serializable
data class Manga(
@SerialName("_id") val id: String,
val name: String,
val thumbnail: String? = null,
val description: String? = null,
val authors: List<String>? = emptyList(),
val genres: List<String>? = emptyList(),
val tags: List<String>? = emptyList(),
val status: String? = null,
val altNames: List<String>? = emptyList(),
val englishName: String? = null,
val nativeName: String? = null,
) {
fun toSManga(titleStyle: String?) = SManga.create().apply {
title = titleStyle.preferedName(name, englishName, nativeName)
url = "/manga/$id/${name.titleToSlug()}"
thumbnail_url = thumbnail?.parseThumbnailUrl()
description = this@Manga.description?.parseDescription()
if (!altNames.isNullOrEmpty()) {
description += altNames.joinToString(
prefix = "\n\nAlternative Names:\n* ",
separator = "\n* ",
) { it.trim() }
}
if (authors?.isNotEmpty() == true) {
author = authors.first().trim()
artist = author
}
genre = "${genres?.joinToString { it.trim() }}, ${tags?.joinToString { it.trim() }}"
status = this@Manga.status.parseStatus()
}
}
}
}
@Serializable
data class Manga(
val _id: String,
val name: String,
val thumbnail: String,
val description: String?,
val authors: List<String>?,
val genres: List<String>?,
val tags: List<String>?,
val status: String?,
val altNames: List<String>?,
val englishName: String? = null,
val nativeName: String? = null,
)
@Serializable
data class ApiChapterListResponse(
val data: ChapterListData,
@ -72,7 +110,7 @@ data class ApiChapterListResponse(
) {
@Serializable
data class ChapterList(
val availableChaptersDetail: AvailableChapters,
@SerialName("availableChaptersDetail") val chapters: AvailableChapters,
) {
@Serializable
data class AvailableChapters(
@ -82,21 +120,43 @@ data class ApiChapterListResponse(
}
}
@Serializable
data class ApiChapterListDetailsResponse(
val data: ChapterListData,
) {
@Serializable
data class ChapterListData(
@SerialName("episodeInfos") val chapterList: List<ChapterData>? = emptyList(),
) {
@Serializable
data class ChapterData(
@SerialName("episodeIdNum") val chapterNum: Float,
@SerialName("notes") val title: String? = null,
val uploadDates: DateDto? = null,
) {
@Serializable
data class DateDto(
val sub: String? = null,
)
}
}
}
@Serializable
data class ApiPageListResponse(
val data: PageListData,
) {
@Serializable
data class PageListData(
val chapterPages: PageList?,
@SerialName("chapterPages") val pageList: PageList?,
) {
@Serializable
data class PageList(
val edges: List<Servers>?,
@SerialName("edges") val serverList: List<Servers>?,
) {
@Serializable
data class Servers(
val pictureUrlHead: String? = null,
@SerialName("pictureUrlHead") val serverUrl: String? = null,
val pictureUrls: List<PageUrl>,
) {
@Serializable
@ -107,3 +167,41 @@ data class ApiPageListResponse(
}
}
}
fun String.parseThumbnailUrl(): String {
return if (this.matches(AllAnime.urlRegex)) {
this
} else {
"${AllAnime.thumbnail_cdn}$this?w=250"
}
}
fun String?.parseStatus(): Int {
if (this == null) {
return SManga.UNKNOWN
}
return when {
this.contains("releasing", true) -> SManga.ONGOING
this.contains("finished", true) -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
}
private fun String.titleToSlug() = this.trim()
.lowercase(Locale.US)
.replace(AllAnime.titleSpecialCharactersRegex, "-")
private fun String?.preferedName(name: String, englishName: String?, nativeName: String?): String {
return when (this) {
"eng" -> englishName
"native" -> nativeName
else -> name
} ?: name
}
private fun String.parseDescription(): String {
return Jsoup.parse(
this.replace("<br>", "br2n"),
).text().replace("br2n", "\n")
}

View File

@ -11,7 +11,13 @@ internal class CountryFilter(name: String, private val countries: List<Pair<Stri
}
internal class GenreFilter(title: String, genres: List<Genre>) :
Filter.Group<Genre>(title, genres)
Filter.Group<Genre>(title, genres) {
val included: List<String>
get() = state.filter { it.isIncluded() }.map { it.name }
val excluded: List<String>
get() = state.filter { it.isExcluded() }.map { it.name }
}
private val genreList: List<Genre> = listOf(
Genre("4 Koma"),

View File

@ -0,0 +1,163 @@
package eu.kanade.tachiyomi.extension.en.allanime
import kotlinx.serialization.Serializable
@Serializable
data class ApiPopularPayload(
val variables: ApiPopularVariables,
val query: String,
) {
@Serializable
data class ApiPopularVariables(
val type: String,
val size: Int,
val dateRange: Int,
val page: Int,
val allowAdult: Boolean,
val allowUnknown: Boolean,
)
constructor(
type: String = "manga",
size: Int,
dateRange: Int,
page: Int,
allowAdult: Boolean = false,
allowUnknown: Boolean = false,
) : this(
ApiPopularVariables(
type = type,
size = size,
dateRange = dateRange,
page = page,
allowAdult = allowAdult,
allowUnknown = allowUnknown,
),
POPULAR_QUERY,
)
}
@Serializable
data class ApiSearchPayload(
val variables: ApiSearchVariables,
val query: String,
) {
@Serializable
data class ApiSearchVariables(
val search: SearchPayload,
val limit: Int,
val page: Int,
val translationType: String,
val countryOrigin: String,
)
@Serializable
data class SearchPayload(
val query: String,
val genres: List<String>?,
val excludeGenres: List<String>?,
val isManga: Boolean,
val allowAdult: Boolean,
val allowUnknown: Boolean,
)
constructor(
query: String,
size: Int,
page: Int,
genres: List<String>?,
excludeGenres: List<String>?,
translationType: String,
countryOrigin: String,
isManga: Boolean = true,
allowAdult: Boolean = false,
allowUnknown: Boolean = false,
) : this(
ApiSearchVariables(
search = SearchPayload(
query = query,
genres = genres,
excludeGenres = excludeGenres,
isManga = isManga,
allowAdult = allowAdult,
allowUnknown = allowUnknown,
),
limit = size,
page = page,
translationType = translationType,
countryOrigin = countryOrigin,
),
SEARCH_QUERY,
)
}
@Serializable
data class ApiIDPayload(
val variables: ApiIDVariables,
val query: String,
) {
@Serializable
data class ApiIDVariables(
val id: String,
)
constructor(
id: String,
graphqlQuery: String,
) : this(
ApiIDVariables(id),
graphqlQuery,
)
}
@Serializable
data class ApiChapterListDetailsPayload(
val variables: ApiChapterDetailsVariables,
val query: String,
) {
@Serializable
data class ApiChapterDetailsVariables(
val id: String,
val chapterNumStart: Float,
val chapterNumEnd: Float,
)
constructor(
id: String,
chapterNumStart: Float,
chapterNumEnd: Float,
) : this(
ApiChapterDetailsVariables(
id = "manga@$id",
chapterNumStart = chapterNumStart,
chapterNumEnd = chapterNumEnd,
),
CHAPTERS_DETAILS_QUERY,
)
}
@Serializable
data class ApiPageListPayload(
val variables: ApiPageListVariables,
val query: String,
) {
@Serializable
data class ApiPageListVariables(
val id: String,
val chapterNum: String,
val translationType: String,
)
constructor(
id: String,
chapterNum: String,
translationType: String,
) : this(
ApiPageListVariables(
id = id,
chapterNum = chapterNum,
translationType = translationType,
),
PAGE_QUERY,
)
}

View File

@ -1,21 +1,24 @@
package eu.kanade.tachiyomi.extension.en.allanime
fun buildQuery(queryAction: () -> String): String {
import eu.kanade.tachiyomi.extension.en.allanime.AllAnime.Companion.whitespace
private fun buildQuery(queryAction: () -> String): String {
return queryAction()
.trimIndent()
.replace(whitespace, " ")
.replace("%", "$")
}
val POPULAR_QUERY: String = buildQuery {
"""
query(
%type: VaildPopularTypeEnumType!
%size: Int!
%page: Int
%dateRange: Int
%allowAdult: Boolean
%allowUnknown: Boolean
) {
query (
%type: VaildPopularTypeEnumType!
%size: Int!
%page: Int
%dateRange: Int
%allowAdult: Boolean
%allowUnknown: Boolean
) {
queryPopular(
type: %type
size: %size
@ -40,13 +43,13 @@ val POPULAR_QUERY: String = buildQuery {
val SEARCH_QUERY: String = buildQuery {
"""
query(
%search: SearchInput
%limit: Int
%page: Int
%translationType: VaildTranslationTypeMangaEnumType
%countryOrigin: VaildCountryOriginEnumType
) {
query (
%search: SearchInput
%limit: Int
%page: Int
%translationType: VaildTranslationTypeMangaEnumType
%countryOrigin: VaildCountryOriginEnumType
) {
mangas(
search: %search
limit: %limit
@ -68,10 +71,8 @@ val SEARCH_QUERY: String = buildQuery {
val DETAILS_QUERY: String = buildQuery {
"""
query (%_id: String!) {
manga(
_id: %_id
) {
query (%id: String!) {
manga(_id: %id) {
_id
name
thumbnail
@ -90,27 +91,41 @@ val DETAILS_QUERY: String = buildQuery {
val CHAPTERS_QUERY: String = buildQuery {
"""
query (%_id: String!) {
manga(
_id: %_id
) {
query (%id: String!) {
manga(_id: %id) {
availableChaptersDetail
}
}
"""
}
val CHAPTERS_DETAILS_QUERY: String = buildQuery {
"""
query (%id: String!, %chapterNumStart: Float!, %chapterNumEnd: Float!) {
episodeInfos(
showId: %id
episodeNumStart: %chapterNumStart
episodeNumEnd: %chapterNumEnd
) {
episodeIdNum
notes
uploadDates
}
}
"""
}
val PAGE_QUERY: String = buildQuery {
"""
query(
%mangaId: String!,
%translationType: VaildTranslationTypeMangaEnumType!,
%chapterString: String!
) {
query (
%id: String!
%translationType: VaildTranslationTypeMangaEnumType!
%chapterNum: String!
) {
chapterPages(
mangaId: %mangaId
mangaId: %id
translationType: %translationType
chapterString: %chapterString
chapterString: %chapterNum
) {
edges {
pictureUrls