|
|
|
@ -22,7 +22,6 @@ import org.koitharu.kotatsu.parsers.util.*
|
|
|
|
import java.util.*
|
|
|
|
import java.util.*
|
|
|
|
import java.util.Collections.emptyList
|
|
|
|
import java.util.Collections.emptyList
|
|
|
|
import java.util.concurrent.TimeUnit
|
|
|
|
import java.util.concurrent.TimeUnit
|
|
|
|
import kotlin.math.pow
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private const val DOMAIN_UNAUTHORIZED = "e-hentai.org"
|
|
|
|
private const val DOMAIN_UNAUTHORIZED = "e-hentai.org"
|
|
|
|
private const val DOMAIN_AUTHORIZED = "exhentai.org"
|
|
|
|
private const val DOMAIN_AUTHORIZED = "exhentai.org"
|
|
|
|
@ -46,7 +45,6 @@ internal class ExHentaiParser(
|
|
|
|
|
|
|
|
|
|
|
|
private val ratingPattern = Regex("-?[0-9]+px")
|
|
|
|
private val ratingPattern = Regex("-?[0-9]+px")
|
|
|
|
private val authCookies = arrayOf("ipb_member_id", "ipb_pass_hash")
|
|
|
|
private val authCookies = arrayOf("ipb_member_id", "ipb_pass_hash")
|
|
|
|
private var updateDm = false
|
|
|
|
|
|
|
|
private val nextPages = SparseArrayCompat<Long>()
|
|
|
|
private val nextPages = SparseArrayCompat<Long>()
|
|
|
|
private val suspiciousContentKey = ConfigKey.ShowSuspiciousContent(false)
|
|
|
|
private val suspiciousContentKey = ConfigKey.ShowSuspiciousContent(false)
|
|
|
|
|
|
|
|
|
|
|
|
@ -113,6 +111,15 @@ internal class ExHentaiParser(
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
|
|
|
|
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
|
|
|
|
|
|
|
|
return getListPage(page, order, filter, updateDm = false)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private suspend fun getListPage(
|
|
|
|
|
|
|
|
page: Int,
|
|
|
|
|
|
|
|
order: SortOrder,
|
|
|
|
|
|
|
|
filter: MangaListFilter,
|
|
|
|
|
|
|
|
updateDm: Boolean,
|
|
|
|
|
|
|
|
): List<Manga> {
|
|
|
|
val next = nextPages.get(page, 0L)
|
|
|
|
val next = nextPages.get(page, 0L)
|
|
|
|
|
|
|
|
|
|
|
|
if (page > 0 && next == 0L) {
|
|
|
|
if (page > 0 && next == 0L) {
|
|
|
|
@ -137,15 +144,18 @@ internal class ExHentaiParser(
|
|
|
|
url.addQueryParameter("f_sh", "on")
|
|
|
|
url.addQueryParameter("f_sh", "on")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
val body = webClient.httpGet(url.build()).parseHtml().body()
|
|
|
|
val body = webClient.httpGet(url.build()).parseHtml().body()
|
|
|
|
val root = body.selectFirst("table.itg")
|
|
|
|
val root = body.selectFirst("table.itg")?.selectFirst("tbody")
|
|
|
|
?.selectFirst("tbody")
|
|
|
|
if (root == null) {
|
|
|
|
?: if (updateDm) {
|
|
|
|
if (updateDm) {
|
|
|
|
|
|
|
|
if (body.getElementsContainingText("No hits found").isNotEmpty()) {
|
|
|
|
|
|
|
|
return emptyList()
|
|
|
|
|
|
|
|
} else {
|
|
|
|
body.parseFailed("Cannot find root")
|
|
|
|
body.parseFailed("Cannot find root")
|
|
|
|
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
updateDm = true
|
|
|
|
return getListPage(page, order, filter, updateDm = true)
|
|
|
|
return getListPage(page, order, filter)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
updateDm = false
|
|
|
|
|
|
|
|
nextPages[page + 1] = getNextTimestamp(body)
|
|
|
|
nextPages[page + 1] = getNextTimestamp(body)
|
|
|
|
|
|
|
|
|
|
|
|
return root.children().mapNotNull { tr ->
|
|
|
|
return root.children().mapNotNull { tr ->
|
|
|
|
@ -155,13 +165,6 @@ internal class ExHentaiParser(
|
|
|
|
val a = gLink.parents().select("a").first() ?: gLink.parseFailed("link not found")
|
|
|
|
val a = gLink.parents().select("a").first() ?: gLink.parseFailed("link not found")
|
|
|
|
val href = a.attrAsRelativeUrl("href")
|
|
|
|
val href = a.attrAsRelativeUrl("href")
|
|
|
|
val tagsDiv = gLink.nextElementSibling() ?: gLink.parseFailed("tags div not found")
|
|
|
|
val tagsDiv = gLink.nextElementSibling() ?: gLink.parseFailed("tags div not found")
|
|
|
|
val mainTag = td2.selectFirst("div.cn")?.let { div ->
|
|
|
|
|
|
|
|
MangaTag(
|
|
|
|
|
|
|
|
title = div.text().toTitleCase(Locale.ENGLISH),
|
|
|
|
|
|
|
|
key = tagIdByClass(div.classNames()) ?: return@let null,
|
|
|
|
|
|
|
|
source = source,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
Manga(
|
|
|
|
Manga(
|
|
|
|
id = generateUid(href),
|
|
|
|
id = generateUid(href),
|
|
|
|
title = gLink.text().cleanupTitle(),
|
|
|
|
title = gLink.text().cleanupTitle(),
|
|
|
|
@ -171,7 +174,7 @@ internal class ExHentaiParser(
|
|
|
|
rating = td2.selectFirst("div.ir")?.parseRating() ?: RATING_UNKNOWN,
|
|
|
|
rating = td2.selectFirst("div.ir")?.parseRating() ?: RATING_UNKNOWN,
|
|
|
|
isNsfw = true,
|
|
|
|
isNsfw = true,
|
|
|
|
coverUrl = td1.selectFirst("img")?.absUrl("src").orEmpty(),
|
|
|
|
coverUrl = td1.selectFirst("img")?.absUrl("src").orEmpty(),
|
|
|
|
tags = setOfNotNull(mainTag) + tagsDiv.parseTags(),
|
|
|
|
tags = tagsDiv.parseTags(),
|
|
|
|
state = null,
|
|
|
|
state = null,
|
|
|
|
author = tagsDiv.getElementsContainingOwnText("artist:").first()
|
|
|
|
author = tagsDiv.getElementsContainingOwnText("artist:").first()
|
|
|
|
?.nextElementSibling()?.text(),
|
|
|
|
?.nextElementSibling()?.text(),
|
|
|
|
@ -250,8 +253,9 @@ internal class ExHentaiParser(
|
|
|
|
return doc.body().requireElementById("img").attrAsAbsoluteUrl("src")
|
|
|
|
return doc.body().requireElementById("img").attrAsAbsoluteUrl("src")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private val tags =
|
|
|
|
@Suppress("SpellCheckingInspection")
|
|
|
|
"ahegao,anal,angel,apron,bandages,bbw,bdsm,beauty mark,big areolae,big ass,big breasts,big clit,big lips," +
|
|
|
|
private val tags: String
|
|
|
|
|
|
|
|
get() = "ahegao,anal,angel,apron,bandages,bbw,bdsm,beauty mark,big areolae,big ass,big breasts,big clit,big lips," +
|
|
|
|
"big nipples,bikini,blackmail,bloomers,blowjob,bodysuit,bondage,breast expansion,bukkake,bunny girl,business suit," +
|
|
|
|
"big nipples,bikini,blackmail,bloomers,blowjob,bodysuit,bondage,breast expansion,bukkake,bunny girl,business suit," +
|
|
|
|
"catgirl,centaur,cheating,chinese dress,christmas,collar,corset,cosplaying,cowgirl,crossdressing,cunnilingus," +
|
|
|
|
"catgirl,centaur,cheating,chinese dress,christmas,collar,corset,cosplaying,cowgirl,crossdressing,cunnilingus," +
|
|
|
|
"dark skin,daughter,deepthroat,defloration,demon girl,double penetration,dougi,dragon,drunk,elf,exhibitionism,farting," +
|
|
|
|
"dark skin,daughter,deepthroat,defloration,demon girl,double penetration,dougi,dragon,drunk,elf,exhibitionism,farting," +
|
|
|
|
@ -387,12 +391,6 @@ internal class ExHentaiParser(
|
|
|
|
return result
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private fun tagIdByClass(classNames: Collection<String>): String? {
|
|
|
|
|
|
|
|
val className = classNames.find { x -> x.startsWith("ct") } ?: return null
|
|
|
|
|
|
|
|
val num = className.drop(2).toIntOrNull(16) ?: return null
|
|
|
|
|
|
|
|
return 2.0.pow(num).toInt().toString()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private fun getNextTimestamp(root: Element): Long {
|
|
|
|
private fun getNextTimestamp(root: Element): Long {
|
|
|
|
return root.getElementById("unext")
|
|
|
|
return root.getElementById("unext")
|
|
|
|
?.attrAsAbsoluteUrlOrNull("href")
|
|
|
|
?.attrAsAbsoluteUrlOrNull("href")
|
|
|
|
|