MangaParser interface
parent
a4827d1b7d
commit
29cf04c804
@ -0,0 +1,128 @@
|
|||||||
|
package org.koitharu.kotatsu.parsers
|
||||||
|
|
||||||
|
import androidx.annotation.CallSuper
|
||||||
|
import okhttp3.Headers
|
||||||
|
import okhttp3.HttpUrl
|
||||||
|
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||||
|
import org.koitharu.kotatsu.parsers.config.MangaSourceConfig
|
||||||
|
import org.koitharu.kotatsu.parsers.model.*
|
||||||
|
import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
|
||||||
|
import org.koitharu.kotatsu.parsers.model.search.MangaSearchQueryCapabilities
|
||||||
|
import org.koitharu.kotatsu.parsers.network.OkHttpWebClient
|
||||||
|
import org.koitharu.kotatsu.parsers.network.WebClient
|
||||||
|
import org.koitharu.kotatsu.parsers.util.*
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
@InternalParsersApi
|
||||||
|
public abstract class AbstractMangaParser @InternalParsersApi constructor(
|
||||||
|
@property:InternalParsersApi public val context: MangaLoaderContext,
|
||||||
|
public override val source: MangaParserSource,
|
||||||
|
) : MangaParser {
|
||||||
|
|
||||||
|
@Deprecated("Please check searchQueryCapabilities")
|
||||||
|
public abstract val filterCapabilities: MangaListFilterCapabilities
|
||||||
|
|
||||||
|
public override val searchQueryCapabilities: MangaSearchQueryCapabilities
|
||||||
|
get() = filterCapabilities.toMangaSearchQueryCapabilities()
|
||||||
|
|
||||||
|
public override val config: MangaSourceConfig by lazy { context.getConfig(source) }
|
||||||
|
|
||||||
|
public open val sourceLocale: Locale
|
||||||
|
get() = if (source.locale.isEmpty()) Locale.ROOT else Locale(source.locale)
|
||||||
|
|
||||||
|
protected val isNsfwSource: Boolean = source.contentType == ContentType.HENTAI
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide default domain and available alternatives, if any.
|
||||||
|
*
|
||||||
|
* Never hardcode domain in requests, use [domain] instead.
|
||||||
|
*/
|
||||||
|
@InternalParsersApi
|
||||||
|
public abstract val configKeyDomain: ConfigKey.Domain
|
||||||
|
|
||||||
|
protected open val userAgentKey: ConfigKey.UserAgent = ConfigKey.UserAgent(context.getDefaultUserAgent())
|
||||||
|
|
||||||
|
override fun getRequestHeaders(): Headers = Headers.Builder()
|
||||||
|
.add("User-Agent", config[userAgentKey])
|
||||||
|
.build()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used as fallback if value of `order` passed to [getList] is null
|
||||||
|
*/
|
||||||
|
public open val defaultSortOrder: SortOrder
|
||||||
|
get() {
|
||||||
|
val supported = availableSortOrders
|
||||||
|
return SortOrder.entries.first { it in supported }
|
||||||
|
}
|
||||||
|
|
||||||
|
override val domain: String
|
||||||
|
get() = config[configKeyDomain]
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
protected val webClient: WebClient = OkHttpWebClient(context.httpClient, source)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search list of manga by specified searchQuery
|
||||||
|
*
|
||||||
|
* @param searchQuery searchQuery
|
||||||
|
*/
|
||||||
|
public override suspend fun queryManga(searchQuery: MangaSearchQuery): List<Manga> {
|
||||||
|
if (!searchQuery.skipValidation) {
|
||||||
|
searchQueryCapabilities.validate(searchQuery)
|
||||||
|
}
|
||||||
|
|
||||||
|
return getList(searchQuery)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search list of manga by specified searchQuery
|
||||||
|
*
|
||||||
|
* @param query searchQuery
|
||||||
|
*/
|
||||||
|
protected open suspend fun getList(query: MangaSearchQuery): List<Manga> = getList(
|
||||||
|
offset = query.offset,
|
||||||
|
order = query.order ?: defaultSortOrder,
|
||||||
|
filter = convertToMangaListFilter(query),
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 order one of [availableSortOrders] or [defaultSortOrder] for default value
|
||||||
|
* @param filter is a set of filter rules
|
||||||
|
*
|
||||||
|
* @deprecated New [getList] should be preferred.
|
||||||
|
*/
|
||||||
|
@Deprecated("New getList(query: MangaSearchQuery) method should be preferred")
|
||||||
|
public abstract suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilter): List<Manga>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch direct link to the page image.
|
||||||
|
*/
|
||||||
|
public override suspend fun getPageUrl(page: MangaPage): String = page.url.toAbsoluteUrl(domain)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse favicons from the main page of the source`s website
|
||||||
|
*/
|
||||||
|
public override suspend fun getFavicons(): Favicons {
|
||||||
|
return FaviconParser(webClient, domain).parseFavicons()
|
||||||
|
}
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
public override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
|
||||||
|
keys.add(configKeyDomain)
|
||||||
|
}
|
||||||
|
|
||||||
|
public override suspend fun getRelatedManga(seed: Manga): List<Manga> {
|
||||||
|
return RelatedMangaFinder(listOf(this)).invoke(seed)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return [Manga] object by web link to it
|
||||||
|
* @see [Manga.publicUrl]
|
||||||
|
*/
|
||||||
|
internal open suspend fun resolveLink(resolver: LinkResolver, link: HttpUrl): Manga? = null
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,145 +1,60 @@
|
|||||||
package org.koitharu.kotatsu.parsers
|
package org.koitharu.kotatsu.parsers
|
||||||
|
|
||||||
import androidx.annotation.CallSuper
|
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
import okhttp3.HttpUrl
|
|
||||||
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||||
import org.koitharu.kotatsu.parsers.config.MangaSourceConfig
|
import org.koitharu.kotatsu.parsers.config.MangaSourceConfig
|
||||||
import org.koitharu.kotatsu.parsers.model.*
|
import org.koitharu.kotatsu.parsers.model.*
|
||||||
import org.koitharu.kotatsu.parsers.model.search.*
|
import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
|
||||||
import org.koitharu.kotatsu.parsers.network.OkHttpWebClient
|
import org.koitharu.kotatsu.parsers.model.search.MangaSearchQueryCapabilities
|
||||||
import org.koitharu.kotatsu.parsers.network.WebClient
|
|
||||||
import org.koitharu.kotatsu.parsers.util.*
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
public abstract class MangaParser @InternalParsersApi constructor(
|
public interface MangaParser {
|
||||||
@property:InternalParsersApi public val context: MangaLoaderContext,
|
|
||||||
public val source: MangaParserSource,
|
public val source: MangaParserSource
|
||||||
) {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Supported [SortOrder] variants. Must not be empty.
|
* Supported [SortOrder] variants. Must not be empty.
|
||||||
*
|
*
|
||||||
* For better performance use [EnumSet] for more than one item.
|
* For better performance use [EnumSet] for more than one item.
|
||||||
*/
|
*/
|
||||||
public abstract val availableSortOrders: Set<SortOrder>
|
public val availableSortOrders: Set<SortOrder>
|
||||||
|
|
||||||
@Deprecated("Please check searchQueryCapabilities")
|
|
||||||
public abstract val filterCapabilities: MangaListFilterCapabilities
|
|
||||||
|
|
||||||
public open val searchQueryCapabilities: MangaSearchQueryCapabilities
|
|
||||||
get() = filterCapabilities.toMangaSearchQueryCapabilities()
|
|
||||||
|
|
||||||
public val config: MangaSourceConfig by lazy { context.getConfig(source) }
|
|
||||||
|
|
||||||
public open val sourceLocale: Locale
|
|
||||||
get() = if (source.locale.isEmpty()) Locale.ROOT else Locale(source.locale)
|
|
||||||
|
|
||||||
protected val isNsfwSource: Boolean = source.contentType == ContentType.HENTAI
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provide default domain and available alternatives, if any.
|
|
||||||
*
|
|
||||||
* Never hardcode domain in requests, use [domain] instead.
|
|
||||||
*/
|
|
||||||
@InternalParsersApi
|
|
||||||
public abstract val configKeyDomain: ConfigKey.Domain
|
|
||||||
|
|
||||||
protected open val userAgentKey: ConfigKey.UserAgent = ConfigKey.UserAgent(context.getDefaultUserAgent())
|
public val searchQueryCapabilities: MangaSearchQueryCapabilities
|
||||||
|
|
||||||
public open fun getRequestHeaders(): Headers = Headers.Builder()
|
public val config: MangaSourceConfig
|
||||||
.add("User-Agent", config[userAgentKey])
|
|
||||||
.build()
|
|
||||||
|
|
||||||
/**
|
public val domain: String
|
||||||
* Used as fallback if value of `order` passed to [getList] is null
|
|
||||||
*/
|
|
||||||
public open val defaultSortOrder: SortOrder
|
|
||||||
get() {
|
|
||||||
val supported = availableSortOrders
|
|
||||||
return SortOrder.entries.first { it in supported }
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmField
|
public suspend fun queryManga(searchQuery: MangaSearchQuery): List<Manga>
|
||||||
protected val webClient: WebClient = OkHttpWebClient(context.httpClient, source)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Search list of manga by specified searchQuery
|
|
||||||
*
|
|
||||||
* @param searchQuery searchQuery
|
|
||||||
*/
|
|
||||||
public suspend fun queryManga(searchQuery: MangaSearchQuery): List<Manga> {
|
|
||||||
if (!searchQuery.skipValidation) {
|
|
||||||
searchQueryCapabilities.validate(searchQuery)
|
|
||||||
}
|
|
||||||
|
|
||||||
return getList(searchQuery)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Search list of manga by specified searchQuery
|
|
||||||
*
|
|
||||||
* @param query searchQuery
|
|
||||||
*/
|
|
||||||
protected open suspend fun getList(query: MangaSearchQuery): List<Manga> = getList(
|
|
||||||
offset = query.offset,
|
|
||||||
order = query.order ?: defaultSortOrder,
|
|
||||||
filter = convertToMangaListFilter(query),
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 order one of [availableSortOrders] or [defaultSortOrder] for default value
|
|
||||||
* @param filter is a set of filter rules
|
|
||||||
*
|
|
||||||
* @deprecated New [getList] should be preferred.
|
|
||||||
*/
|
|
||||||
@Deprecated("New getList(query: MangaSearchQuery) method should be preferred")
|
|
||||||
public abstract suspend fun getList(offset: Int, order: SortOrder, filter: MangaListFilter): List<Manga>
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
* @see Manga.copy
|
* @see Manga.copy
|
||||||
*/
|
*/
|
||||||
public abstract suspend fun getDetails(manga: Manga): Manga
|
public suspend fun getDetails(manga: Manga): Manga
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse pages list for specified chapter.
|
* Parse pages list for specified chapter.
|
||||||
* @see MangaPage for details
|
* @see MangaPage for details
|
||||||
*/
|
*/
|
||||||
public abstract suspend fun getPages(chapter: MangaChapter): List<MangaPage>
|
public suspend fun getPages(chapter: MangaChapter): List<MangaPage>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch direct link to the page image.
|
* Fetch direct link to the page image.
|
||||||
*/
|
*/
|
||||||
public open suspend fun getPageUrl(page: MangaPage): String = page.url.toAbsoluteUrl(domain)
|
public suspend fun getPageUrl(page: MangaPage): String
|
||||||
|
|
||||||
public abstract suspend fun getFilterOptions(): MangaListFilterOptions
|
public suspend fun getFilterOptions(): MangaListFilterOptions
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse favicons from the main page of the source`s website
|
* Parse favicons from the main page of the source`s website
|
||||||
*/
|
*/
|
||||||
public open suspend fun getFavicons(): Favicons {
|
public suspend fun getFavicons(): Favicons
|
||||||
return FaviconParser(webClient, domain).parseFavicons()
|
|
||||||
}
|
|
||||||
|
|
||||||
@CallSuper
|
|
||||||
public open fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
|
|
||||||
keys.add(configKeyDomain)
|
|
||||||
}
|
|
||||||
|
|
||||||
public open suspend fun getRelatedManga(seed: Manga): List<Manga> {
|
public fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>)
|
||||||
return RelatedMangaFinder(listOf(this)).invoke(seed)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
public suspend fun getRelatedManga(seed: Manga): List<Manga>
|
||||||
* Return [Manga] object by web link to it
|
|
||||||
* @see [Manga.publicUrl]
|
|
||||||
*/
|
|
||||||
internal open suspend fun resolveLink(resolver: LinkResolver, link: HttpUrl): Manga? = null
|
|
||||||
|
|
||||||
|
public fun getRequestHeaders(): Headers
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue