213 lines
8.6 KiB
Kotlin
213 lines
8.6 KiB
Kotlin
package exh.md.handlers
|
|
|
|
import eu.kanade.tachiyomi.network.GET
|
|
import eu.kanade.tachiyomi.network.asObservableSuccess
|
|
import eu.kanade.tachiyomi.source.model.FilterList
|
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
|
import eu.kanade.tachiyomi.source.model.SManga
|
|
import eu.kanade.tachiyomi.util.asJsoup
|
|
import exh.md.utils.MdUtil
|
|
import exh.md.utils.setMDUrlWithoutDomain
|
|
import okhttp3.CacheControl
|
|
import okhttp3.Headers
|
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
|
import okhttp3.OkHttpClient
|
|
import okhttp3.Request
|
|
import okhttp3.Response
|
|
import org.jsoup.nodes.Element
|
|
import rx.Observable
|
|
|
|
// Unused, kept for reference todo
|
|
class SearchHandler(val client: OkHttpClient, private val headers: Headers, val lang: String, private val useLowQualityCovers: Boolean) {
|
|
|
|
fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
|
return when {
|
|
query.startsWith(PREFIX_ID_SEARCH) -> {
|
|
val realQuery = query.removePrefix(PREFIX_ID_SEARCH)
|
|
client.newCall(searchMangaByIdRequest(realQuery))
|
|
.asObservableSuccess()
|
|
.map { response ->
|
|
val details = SManga.create()
|
|
details.url = "/manga/$realQuery/"
|
|
ApiMangaParser(lang).parseToManga(details, response, emptyList()).await()
|
|
MangasPage(listOf(details), false)
|
|
}
|
|
}
|
|
query.startsWith(PREFIX_GROUP_SEARCH) -> {
|
|
val realQuery = query.removePrefix(PREFIX_GROUP_SEARCH)
|
|
client.newCall(searchMangaByGroupRequest(realQuery))
|
|
.asObservableSuccess()
|
|
.map { response ->
|
|
response.asJsoup().select(groupSelector).firstOrNull()?.attr("abs:href")
|
|
?.let {
|
|
searchMangaParse(client.newCall(GET("$it/manga/0", headers)).execute())
|
|
}
|
|
?: MangasPage(emptyList(), false)
|
|
}
|
|
}
|
|
else -> {
|
|
client.newCall(searchMangaRequest(page, query, filters))
|
|
.asObservableSuccess()
|
|
.map { response ->
|
|
searchMangaParse(response)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun searchMangaParse(response: Response): MangasPage {
|
|
val document = response.asJsoup()
|
|
|
|
val mangas = document.select(searchMangaSelector).map { element ->
|
|
searchMangaFromElement(element)
|
|
}
|
|
|
|
val hasNextPage = searchMangaNextPageSelector.let { selector ->
|
|
document.select(selector).first()
|
|
} != null
|
|
|
|
return MangasPage(mangas, hasNextPage)
|
|
}
|
|
|
|
private fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
|
val tags = mutableListOf<String>()
|
|
val statuses = mutableListOf<String>()
|
|
val demographics = mutableListOf<String>()
|
|
|
|
// Do traditional search
|
|
val url = "${MdUtil.baseUrl}/?page=search".toHttpUrlOrNull()!!.newBuilder()
|
|
.addQueryParameter("p", page.toString())
|
|
.addQueryParameter("title", query.replace(WHITESPACE_REGEX, " "))
|
|
|
|
filters.forEach { filter ->
|
|
when (filter) {
|
|
is FilterHandler.TextField -> url.addQueryParameter(filter.key, filter.state)
|
|
is FilterHandler.DemographicList -> {
|
|
filter.state.forEach { demographic ->
|
|
if (demographic.state) {
|
|
demographics.add(demographic.id)
|
|
}
|
|
}
|
|
}
|
|
is FilterHandler.PublicationStatusList -> {
|
|
filter.state.forEach { status ->
|
|
if (status.state) {
|
|
statuses.add(status.id)
|
|
}
|
|
}
|
|
}
|
|
is FilterHandler.OriginalLanguage -> {
|
|
if (filter.state != 0) {
|
|
val number: String =
|
|
FilterHandler.sourceLang().first { it -> it.first == filter.values[filter.state] }
|
|
.second
|
|
url.addQueryParameter("lang_id", number)
|
|
}
|
|
}
|
|
is FilterHandler.TagInclusionMode -> {
|
|
url.addQueryParameter("tag_mode_inc", arrayOf("all", "any")[filter.state])
|
|
}
|
|
is FilterHandler.TagExclusionMode -> {
|
|
url.addQueryParameter("tag_mode_exc", arrayOf("all", "any")[filter.state])
|
|
}
|
|
is FilterHandler.ContentList -> {
|
|
filter.state.forEach { content ->
|
|
if (content.isExcluded()) {
|
|
tags.add("-${content.id}")
|
|
} else if (content.isIncluded()) {
|
|
tags.add(content.id)
|
|
}
|
|
}
|
|
}
|
|
is FilterHandler.FormatList -> {
|
|
filter.state.forEach { format ->
|
|
if (format.isExcluded()) {
|
|
tags.add("-${format.id}")
|
|
} else if (format.isIncluded()) {
|
|
tags.add(format.id)
|
|
}
|
|
}
|
|
}
|
|
is FilterHandler.GenreList -> {
|
|
filter.state.forEach { genre ->
|
|
if (genre.isExcluded()) {
|
|
tags.add("-${genre.id}")
|
|
} else if (genre.isIncluded()) {
|
|
tags.add(genre.id)
|
|
}
|
|
}
|
|
}
|
|
is FilterHandler.ThemeList -> {
|
|
filter.state.forEach { theme ->
|
|
if (theme.isExcluded()) {
|
|
tags.add("-${theme.id}")
|
|
} else if (theme.isIncluded()) {
|
|
tags.add(theme.id)
|
|
}
|
|
}
|
|
}
|
|
is FilterHandler.SortFilter -> {
|
|
if (filter.state != null) {
|
|
val sortables = FilterHandler.sortables()
|
|
if (filter.state!!.ascending) {
|
|
url.addQueryParameter(
|
|
"s",
|
|
sortables[filter.state!!.index].second.toString()
|
|
)
|
|
} else {
|
|
url.addQueryParameter(
|
|
"s",
|
|
sortables[filter.state!!.index].third.toString()
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Manually append genres list to avoid commas being encoded
|
|
var urlToUse = url.toString()
|
|
if (demographics.isNotEmpty()) {
|
|
urlToUse += "&demos=" + demographics.joinToString(",")
|
|
}
|
|
if (statuses.isNotEmpty()) {
|
|
urlToUse += "&statuses=" + statuses.joinToString(",")
|
|
}
|
|
if (tags.isNotEmpty()) {
|
|
urlToUse += "&tags=" + tags.joinToString(",")
|
|
}
|
|
|
|
return GET(urlToUse, headers, CacheControl.FORCE_NETWORK)
|
|
}
|
|
|
|
private fun searchMangaFromElement(element: Element): SManga {
|
|
val manga = SManga.create()
|
|
element.select("a.manga_title").first().let {
|
|
val url = MdUtil.modifyMangaUrl(it.attr("href"))
|
|
manga.setMDUrlWithoutDomain(url)
|
|
manga.title = it.text().trim()
|
|
}
|
|
|
|
manga.thumbnail_url = MdUtil.formThumbUrl(manga.url, useLowQualityCovers)
|
|
|
|
return manga
|
|
}
|
|
|
|
private fun searchMangaByIdRequest(id: String): Request {
|
|
return GET(MdUtil.apiUrl + MdUtil.apiManga + id + MdUtil.includeChapters, headers, CacheControl.FORCE_NETWORK)
|
|
}
|
|
|
|
private fun searchMangaByGroupRequest(group: String): Request {
|
|
return GET(MdUtil.groupSearchUrl + group, headers, CacheControl.FORCE_NETWORK)
|
|
}
|
|
|
|
companion object {
|
|
const val PREFIX_ID_SEARCH = "id:"
|
|
const val PREFIX_GROUP_SEARCH = "group:"
|
|
val WHITESPACE_REGEX = "\\s".toRegex()
|
|
const val searchMangaNextPageSelector =
|
|
".pagination li:not(.disabled) span[title*=last page]:not(disabled)"
|
|
const val searchMangaSelector = "div.manga-entry"
|
|
const val groupSelector = ".table > tbody:nth-child(2) > tr:nth-child(1) > td:nth-child(2) > a"
|
|
}
|
|
}
|