Refactor getList function

pull/19/head
Koitharu 4 years ago
parent 22baf09cc3
commit d724460332
No known key found for this signature in database
GPG Key ID: 8E861F8CE6E7CE27

@ -73,8 +73,6 @@ class ParserProcessor(
""" """
package org.koitharu.kotatsu.parsers.model package org.koitharu.kotatsu.parsers.model
import org.koitharu.kotatsu.parsers.model.MangaSource
enum class MangaSource( enum class MangaSource(
val title: String, val title: String,
val locale: String?, val locale: String?,

@ -7,7 +7,7 @@ import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl
import java.util.* import java.util.*
abstract class MangaParser(val source: MangaSource) { abstract class MangaParser @InternalParsersApi constructor(val source: MangaSource) {
protected abstract val context: MangaLoaderContext protected abstract val context: MangaLoaderContext
@ -27,6 +27,15 @@ abstract class MangaParser(val source: MangaSource) {
*/ */
protected abstract val configKeyDomain: ConfigKey.Domain protected abstract val configKeyDomain: ConfigKey.Domain
/**
* Used as fallback if value of `sortOrder` passed to [getList] is null
*/
protected open val defaultSortOrder: SortOrder
get() {
val supported = sortOrders
return SortOrder.values().first { it in supported }
}
/** /**
* Parse list of manga by specified criteria * Parse list of manga by specified criteria
* *
@ -36,13 +45,36 @@ abstract class MangaParser(val source: MangaSource) {
* @param tags genres for filtering, values from [getTags] and [Manga.tags]. May be null or empty * @param tags genres for filtering, values from [getTags] and [Manga.tags]. May be null or empty
* @param sortOrder one of [sortOrders] or null for default value * @param sortOrder one of [sortOrders] or null for default value
*/ */
@InternalParsersApi
abstract suspend fun getList( abstract suspend fun getList(
offset: Int, offset: Int,
query: String? = null, query: String?,
tags: Set<MangaTag>? = null, tags: Set<MangaTag>?,
sortOrder: SortOrder? = null, sortOrder: SortOrder,
): List<Manga> ): List<Manga>
/**
* Parse list of manga with search by text query
*
* @param offset starting from 0 and used for pagination.
* @param query search query
*/
suspend fun getList(offset: Int, query: String): List<Manga> {
return getList(offset, query, null, defaultSortOrder)
}
/**
* Parse list of manga by specified criteria
*
* @param offset starting from 0 and used for pagination.
* Note than passed value may not be divisible by internal page size, so you should adjust it manually.
* @param tags genres for filtering, values from [getTags] and [Manga.tags]. May be null or empty
* @param sortOrder one of [sortOrders] or null for default value
*/
suspend fun getList(offset: Int, tags: Set<MangaTag>?, sortOrder: SortOrder?): List<Manga> {
return getList(offset, null, tags, sortOrder ?: defaultSortOrder)
}
/** /**
* Parse details for [Manga]: chapters list, description, large cover, etc. * Parse details for [Manga]: chapters list, description, large cover, etc.
* Must return the same manga, may change any fields excepts id, url and source * Must return the same manga, may change any fields excepts id, url and source
@ -121,12 +153,29 @@ abstract class MangaParser(val source: MangaSource) {
/** /**
* Convert relative url to an absolute using [getDomain] * Convert relative url to an absolute using [getDomain]
*/ */
protected fun String.withDomain(subdomain: String? = null): String { @Deprecated(
var domain = getDomain() message = "Use toAbsoluteUrl() instead",
if (subdomain != null) { replaceWith = ReplaceWith(
domain = subdomain + "." + domain.removePrefix("www.") "toAbsoluteUrl(getDomain(), subdomain)",
"org.koitharu.kotatsu.parsers.util.toAbsoluteUrl",
),
)
protected fun String.withDomain(subdomain: String): String {
return toAbsoluteUrl(getDomain(), subdomain)
} }
return toAbsoluteUrl(domain)
/**
* Convert relative url to an absolute using [getDomain]
*/
@Deprecated(
message = "Use toAbsoluteUrl() instead",
replaceWith = ReplaceWith(
"toAbsoluteUrl(getDomain())",
"org.koitharu.kotatsu.parsers.util.toAbsoluteUrl",
),
)
protected fun String.withDomain(): String {
return toAbsoluteUrl(getDomain())
} }
@InternalParsersApi @InternalParsersApi

@ -30,7 +30,7 @@ internal class AnibelParser(override val context: MangaLoaderContext) : MangaPar
offset: Int, offset: Int,
query: String?, query: String?,
tags: Set<MangaTag>?, tags: Set<MangaTag>?,
sortOrder: SortOrder?, sortOrder: SortOrder,
): List<Manga> { ): List<Manga> {
if (!query.isNullOrEmpty()) { if (!query.isNullOrEmpty()) {
return if (offset == 0) { return if (offset == 0) {

@ -39,7 +39,7 @@ internal class BatoToParser(override val context: MangaLoaderContext) : MangaPar
offset: Int, offset: Int,
query: String?, query: String?,
tags: Set<MangaTag>?, tags: Set<MangaTag>?,
sortOrder: SortOrder?, sortOrder: SortOrder,
): List<Manga> { ): List<Manga> {
if (!query.isNullOrEmpty()) { if (!query.isNullOrEmpty()) {
return search(offset, query) return search(offset, query)
@ -52,7 +52,6 @@ internal class BatoToParser(override val context: MangaLoaderContext) : MangaPar
append(getDomain()) append(getDomain())
append("/browse?sort=") append("/browse?sort=")
when (sortOrder) { when (sortOrder) {
null,
SortOrder.UPDATED, SortOrder.UPDATED,
-> append("update.za") -> append("update.za")
SortOrder.POPULARITY -> append("views_a.za") SortOrder.POPULARITY -> append("views_a.za")

@ -19,7 +19,7 @@ internal abstract class ChanParser(source: MangaSource) : MangaParser(source) {
offset: Int, offset: Int,
query: String?, query: String?,
tags: Set<MangaTag>?, tags: Set<MangaTag>?,
sortOrder: SortOrder?, sortOrder: SortOrder,
): List<Manga> { ): List<Manga> {
val domain = getDomain() val domain = getDomain()
val url = when { val url = when {
@ -143,16 +143,16 @@ internal abstract class ChanParser(source: MangaSource) : MangaParser(source) {
} }
} }
private fun getSortKey(sortOrder: SortOrder?) = private fun getSortKey(sortOrder: SortOrder) =
when (sortOrder ?: sortOrders.minByOrNull { it.ordinal }) { when (sortOrder) {
SortOrder.ALPHABETICAL -> "catalog" SortOrder.ALPHABETICAL -> "catalog"
SortOrder.POPULARITY -> "mostfavorites" SortOrder.POPULARITY -> "mostfavorites"
SortOrder.NEWEST -> "manga/new" SortOrder.NEWEST -> "manga/new"
else -> "mostfavorites" else -> "mostfavorites"
} }
private fun getSortKey2(sortOrder: SortOrder?) = private fun getSortKey2(sortOrder: SortOrder) =
when (sortOrder ?: sortOrders.minByOrNull { it.ordinal }) { when (sortOrder) {
SortOrder.ALPHABETICAL -> "abcasc" SortOrder.ALPHABETICAL -> "abcasc"
SortOrder.POPULARITY -> "favdesc" SortOrder.POPULARITY -> "favdesc"
SortOrder.NEWEST -> "datedesc" SortOrder.NEWEST -> "datedesc"

@ -39,7 +39,7 @@ internal class ComickFunParser(override val context: MangaLoaderContext) : Manga
offset: Int, offset: Int,
query: String?, query: String?,
tags: Set<MangaTag>?, tags: Set<MangaTag>?,
sortOrder: SortOrder?, sortOrder: SortOrder,
): List<Manga> { ): List<Manga> {
val domain = getDomain() val domain = getDomain()
val url = buildString { val url = buildString {

@ -28,7 +28,7 @@ internal class DesuMeParser(override val context: MangaLoaderContext) : MangaPar
offset: Int, offset: Int,
query: String?, query: String?,
tags: Set<MangaTag>?, tags: Set<MangaTag>?,
sortOrder: SortOrder?, sortOrder: SortOrder,
): List<Manga> { ): List<Manga> {
if (query != null && offset != 0) { if (query != null && offset != 0) {
return emptyList() return emptyList()
@ -149,7 +149,7 @@ internal class DesuMeParser(override val context: MangaLoaderContext) : MangaPar
} }
} }
private fun getSortKey(sortOrder: SortOrder?) = private fun getSortKey(sortOrder: SortOrder) =
when (sortOrder) { when (sortOrder) {
SortOrder.ALPHABETICAL -> "name" SortOrder.ALPHABETICAL -> "name"
SortOrder.POPULARITY -> "popular" SortOrder.POPULARITY -> "popular"

@ -61,7 +61,7 @@ internal class ExHentaiParser(
offset: Int, offset: Int,
query: String?, query: String?,
tags: Set<MangaTag>?, tags: Set<MangaTag>?,
sortOrder: SortOrder?, sortOrder: SortOrder,
): List<Manga> { ): List<Manga> {
val page = (offset / 25f).toIntUp() val page = (offset / 25f).toIntUp()
var search = query?.urlEncoded().orEmpty() var search = query?.urlEncoded().orEmpty()

@ -5,7 +5,6 @@ import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Response import okhttp3.Response
import org.json.JSONArray import org.json.JSONArray
import org.koitharu.kotatsu.parsers.MangaParser import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.exception.ParseException
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import org.koitharu.kotatsu.parsers.util.json.mapJSON import org.koitharu.kotatsu.parsers.util.json.mapJSON
@ -34,7 +33,7 @@ internal abstract class GroupleParser(source: MangaSource, userAgent: String) :
offset: Int, offset: Int,
query: String?, query: String?,
tags: Set<MangaTag>?, tags: Set<MangaTag>?,
sortOrder: SortOrder?, sortOrder: SortOrder,
): List<Manga> { ): List<Manga> {
val domain = getDomain() val domain = getDomain()
val doc = when { val doc = when {
@ -48,17 +47,13 @@ internal abstract class GroupleParser(source: MangaSource, userAgent: String) :
) )
tags.isNullOrEmpty() -> context.httpGet( tags.isNullOrEmpty() -> context.httpGet(
"https://$domain/list?sortType=${ "https://$domain/list?sortType=${
getSortKey( getSortKey(sortOrder)
sortOrder,
)
}&offset=${offset upBy PAGE_SIZE}", }&offset=${offset upBy PAGE_SIZE}",
headers, headers,
) )
tags.size == 1 -> context.httpGet( tags.size == 1 -> context.httpGet(
"https://$domain/list/genre/${tags.first().key}?sortType=${ "https://$domain/list/genre/${tags.first().key}?sortType=${
getSortKey( getSortKey(sortOrder)
sortOrder,
)
}&offset=${offset upBy PAGE_SIZE}", }&offset=${offset upBy PAGE_SIZE}",
headers, headers,
) )
@ -234,14 +229,13 @@ internal abstract class GroupleParser(source: MangaSource, userAgent: String) :
} }
} }
private fun getSortKey(sortOrder: SortOrder?) = private fun getSortKey(sortOrder: SortOrder) =
when (sortOrder ?: sortOrders.minByOrNull { it.ordinal }) { when (sortOrder) {
SortOrder.ALPHABETICAL -> "name" SortOrder.ALPHABETICAL -> "name"
SortOrder.POPULARITY -> "rate" SortOrder.POPULARITY -> "rate"
SortOrder.UPDATED -> "updated" SortOrder.UPDATED -> "updated"
SortOrder.NEWEST -> "created" SortOrder.NEWEST -> "created"
SortOrder.RATING -> "votes" SortOrder.RATING -> "votes"
null -> "updated"
} }
private suspend fun advancedSearch(domain: String, tags: Set<MangaTag>): Response { private suspend fun advancedSearch(domain: String, tags: Set<MangaTag>): Response {

@ -21,7 +21,7 @@ internal class HenChanParser(override val context: MangaLoaderContext) : ChanPar
offset: Int, offset: Int,
query: String?, query: String?,
tags: Set<MangaTag>?, tags: Set<MangaTag>?,
sortOrder: SortOrder?, sortOrder: SortOrder,
): List<Manga> { ): List<Manga> {
return super.getList(offset, query, tags, sortOrder).map { return super.getList(offset, query, tags, sortOrder).map {
it.copy( it.copy(

@ -34,7 +34,7 @@ internal abstract class MadaraParser(
offset: Int, offset: Int,
query: String?, query: String?,
tags: Set<MangaTag>?, tags: Set<MangaTag>?,
sortOrder: SortOrder?, sortOrder: SortOrder,
): List<Manga> { ): List<Manga> {
val tag = when { val tag = when {
tags.isNullOrEmpty() -> null tags.isNullOrEmpty() -> null

@ -35,7 +35,7 @@ internal class MangaDexParser(override val context: MangaLoaderContext) : MangaP
offset: Int, offset: Int,
query: String?, query: String?,
tags: Set<MangaTag>?, tags: Set<MangaTag>?,
sortOrder: SortOrder?, sortOrder: SortOrder,
): List<Manga> { ): List<Manga> {
val domain = getDomain() val domain = getDomain()
val url = buildString { val url = buildString {
@ -60,7 +60,6 @@ internal class MangaDexParser(override val context: MangaLoaderContext) : MangaP
append("&order") append("&order")
append( append(
when (sortOrder) { when (sortOrder) {
null,
SortOrder.UPDATED, SortOrder.UPDATED,
-> "[latestUploadedChapter]=desc" -> "[latestUploadedChapter]=desc"
SortOrder.ALPHABETICAL -> "[title]=asc" SortOrder.ALPHABETICAL -> "[title]=asc"

@ -23,7 +23,7 @@ class MangaInUaParser(override val context: MangaLoaderContext) : MangaParser(Ma
offset: Int, offset: Int,
query: String?, query: String?,
tags: Set<MangaTag>?, tags: Set<MangaTag>?,
sortOrder: SortOrder?, sortOrder: SortOrder,
): List<Manga> { ): List<Manga> {
val page = (offset / 24f).toIntUp().inc() val page = (offset / 24f).toIntUp().inc()
val searchPage = (offset / 10f).toIntUp().inc() val searchPage = (offset / 10f).toIntUp().inc()

@ -41,7 +41,7 @@ internal open class MangaLibParser(
offset: Int, offset: Int,
query: String?, query: String?,
tags: Set<MangaTag>?, tags: Set<MangaTag>?,
sortOrder: SortOrder?, sortOrder: SortOrder,
): List<Manga> { ): List<Manga> {
if (!query.isNullOrEmpty()) { if (!query.isNullOrEmpty()) {
return if (offset == 0) search(query) else emptyList() return if (offset == 0) search(query) else emptyList()

@ -27,7 +27,7 @@ internal class MangaOwlParser(override val context: MangaLoaderContext) : MangaP
offset: Int, offset: Int,
query: String?, query: String?,
tags: Set<MangaTag>?, tags: Set<MangaTag>?,
sortOrder: SortOrder?, sortOrder: SortOrder,
): List<Manga> { ): List<Manga> {
val page = (offset / 36f).toIntUp().inc() val page = (offset / 36f).toIntUp().inc()
val link = buildString { val link = buildString {
@ -165,16 +165,16 @@ internal class MangaOwlParser(override val context: MangaLoaderContext) : MangaP
} }
} }
private fun getSortKey(sortOrder: SortOrder?) = private fun getSortKey(sortOrder: SortOrder) =
when (sortOrder ?: sortOrders.minByOrNull { it.ordinal }) { when (sortOrder) {
SortOrder.POPULARITY -> "popular" SortOrder.POPULARITY -> "popular"
SortOrder.NEWEST -> "new_release" SortOrder.NEWEST -> "new_release"
SortOrder.UPDATED -> "lastest" SortOrder.UPDATED -> "lastest"
else -> "lastest" else -> "lastest"
} }
private fun getAlternativeSortKey(sortOrder: SortOrder?) = private fun getAlternativeSortKey(sortOrder: SortOrder) =
when (sortOrder ?: sortOrders.minByOrNull { it.ordinal }) { when (sortOrder) {
SortOrder.POPULARITY -> "0" SortOrder.POPULARITY -> "0"
SortOrder.NEWEST -> "2" SortOrder.NEWEST -> "2"
SortOrder.UPDATED -> "3" SortOrder.UPDATED -> "3"

@ -29,7 +29,7 @@ internal class MangaTownParser(override val context: MangaLoaderContext) : Manga
offset: Int, offset: Int,
query: String?, query: String?,
tags: Set<MangaTag>?, tags: Set<MangaTag>?,
sortOrder: SortOrder?, sortOrder: SortOrder,
): List<Manga> { ): List<Manga> {
val sortKey = when (sortOrder) { val sortKey = when (sortOrder) {
SortOrder.ALPHABETICAL -> "?name.az" SortOrder.ALPHABETICAL -> "?name.az"

@ -38,7 +38,7 @@ internal abstract class NineMangaParser(
offset: Int, offset: Int,
query: String?, query: String?,
tags: Set<MangaTag>?, tags: Set<MangaTag>?,
sortOrder: SortOrder?, sortOrder: SortOrder,
): List<Manga> { ): List<Manga> {
val page = (offset / PAGE_SIZE.toFloat()).toIntUp() + 1 val page = (offset / PAGE_SIZE.toFloat()).toIntUp() + 1
val url = buildString { val url = buildString {

@ -48,7 +48,7 @@ internal class NudeMoonParser(
offset: Int, offset: Int,
query: String?, query: String?,
tags: Set<MangaTag>?, tags: Set<MangaTag>?,
sortOrder: SortOrder?, sortOrder: SortOrder,
): List<Manga> { ): List<Manga> {
val domain = getDomain() val domain = getDomain()
val url = when { val url = when {
@ -206,8 +206,8 @@ internal class NudeMoonParser(
return "https://${getDomain()}/favicon.jpg" return "https://${getDomain()}/favicon.jpg"
} }
private fun getSortKey(sortOrder: SortOrder?) = private fun getSortKey(sortOrder: SortOrder) =
when (sortOrder ?: sortOrders.minByOrNull { it.ordinal }) { when (sortOrder) {
SortOrder.POPULARITY -> "views" SortOrder.POPULARITY -> "views"
SortOrder.NEWEST -> "date" SortOrder.NEWEST -> "date"
SortOrder.RATING -> "like" SortOrder.RATING -> "like"

@ -57,7 +57,7 @@ internal class RemangaParser(
offset: Int, offset: Int,
query: String?, query: String?,
tags: Set<MangaTag>?, tags: Set<MangaTag>?,
sortOrder: SortOrder?, sortOrder: SortOrder,
): List<Manga> { ): List<Manga> {
copyCookies() copyCookies()
val domain = getDomain() val domain = getDomain()

@ -105,15 +105,24 @@ fun String.toRelativeUrl(domain: String): String {
} }
/** /**
* Convert url to absolute with specified [domain] * Convert url to absolute with specified domain
* @return an absolute url with [domain] if this is relative * @return an absolute url with [domain] if this is relative
*/ */
fun String.toAbsoluteUrl(domain: String): String = when { fun String.toAbsoluteUrl(domain: String): String = when {
this.startsWith("//") -> "https:$this" this.startsWith("//") -> "https:$this"
this.startsWith("/") -> "https://$domain$this" this.startsWith('/') -> "https://$domain$this"
else -> this else -> this
} }
/**
* Convert url to absolute with specified domain and subdomain
* @return an absolute url with [subdomain].[domain] if this is relative
*/
fun String.toAbsoluteUrl(domain: String, subdomain: String): String {
if (!this.startsWith('/')) return this
return toAbsoluteUrl(subdomain + "." + domain.removePrefix("www."))
}
@Deprecated( @Deprecated(
message = "", message = "",
level = DeprecationLevel.ERROR, level = DeprecationLevel.ERROR,

@ -25,7 +25,7 @@ internal class MangaParserTest {
@MangaSources @MangaSources
fun list(source: MangaSource) = runTest { fun list(source: MangaSource) = runTest {
val parser = source.newParser(context) val parser = source.newParser(context)
val list = parser.getList(20, query = null, sortOrder = SortOrder.POPULARITY, tags = null) val list = parser.getList(20, sortOrder = SortOrder.POPULARITY, tags = null)
checkMangaList(list, "list") checkMangaList(list, "list")
assert(list.all { it.source == source }) assert(list.all { it.source == source })
} }
@ -34,12 +34,12 @@ internal class MangaParserTest {
@MangaSources @MangaSources
fun search(source: MangaSource) = runTest { fun search(source: MangaSource) = runTest {
val parser = source.newParser(context) val parser = source.newParser(context)
val subject = parser.getList(20, query = null, sortOrder = SortOrder.POPULARITY, tags = null).minByOrNull { val subject = parser.getList(20, sortOrder = SortOrder.POPULARITY, tags = null).minByOrNull {
it.title.length it.title.length
} ?: error("No manga found") } ?: error("No manga found")
val query = subject.title val query = subject.title
check(query.isNotBlank()) { "Manga title '$query' is blank" } check(query.isNotBlank()) { "Manga title '$query' is blank" }
val list = parser.getList(offset = 0, query, sortOrder = null, tags = null) val list = parser.getList(0, query)
assert(list.singleOrNull { it.url == subject.url && it.id == subject.id } != null) { assert(list.singleOrNull { it.url == subject.url && it.id == subject.id } != null) {
"Single subject '${subject.title} (${subject.publicUrl})' not found in search results" "Single subject '${subject.title} (${subject.publicUrl})' not found in search results"
} }
@ -62,7 +62,7 @@ internal class MangaParserTest {
assert(tags.all { it.source == source }) assert(tags.all { it.source == source })
val tag = tags.last() val tag = tags.last()
val list = parser.getList(offset = 0, tags = setOf(tag), query = null, sortOrder = null) val list = parser.getList(offset = 0, tags = setOf(tag), sortOrder = null)
checkMangaList(list, "${tag.title} (${tag.key})") checkMangaList(list, "${tag.title} (${tag.key})")
assert(list.all { it.source == source }) assert(list.all { it.source == source })
} }
@ -71,7 +71,7 @@ internal class MangaParserTest {
@MangaSources @MangaSources
fun details(source: MangaSource) = runTest { fun details(source: MangaSource) = runTest {
val parser = source.newParser(context) val parser = source.newParser(context)
val list = parser.getList(20, query = null, sortOrder = SortOrder.POPULARITY, tags = null) val list = parser.getList(20, sortOrder = SortOrder.POPULARITY, tags = null)
val manga = list[3] val manga = list[3]
parser.getDetails(manga).apply { parser.getDetails(manga).apply {
assert(!chapters.isNullOrEmpty()) { "Chapters are null or empty" } assert(!chapters.isNullOrEmpty()) { "Chapters are null or empty" }
@ -100,7 +100,7 @@ internal class MangaParserTest {
@MangaSources @MangaSources
fun pages(source: MangaSource) = runTest { fun pages(source: MangaSource) = runTest {
val parser = source.newParser(context) val parser = source.newParser(context)
val list = parser.getList(20, query = null, sortOrder = SortOrder.POPULARITY, tags = null) val list = parser.getList(20, sortOrder = SortOrder.POPULARITY, tags = null)
val manga = list.first() val manga = list.first()
val chapter = parser.getDetails(manga).chapters?.firstOrNull() ?: error("Chapter is null") val chapter = parser.getDetails(manga).chapters?.firstOrNull() ?: error("Chapter is null")
val pages = parser.getPages(chapter) val pages = parser.getPages(chapter)

Loading…
Cancel
Save