Update dependencies
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
plugins {
|
||||
kotlin("jvm") version "2.0.20"
|
||||
kotlin("jvm") version "2.2.10"
|
||||
}
|
||||
|
||||
repositories {
|
||||
@@ -14,5 +14,5 @@ dependencies {
|
||||
implementation(gradleApi())
|
||||
implementation("org.simpleframework:simple-xml:2.7.1")
|
||||
implementation("com.soywiz.korlibs.korte:korte-jvm:4.0.10")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.1")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
|
||||
distributionSha256Sum=bd71102213493060956ec229d946beee57158dbd89d0e62b91bca0fa2c5f3531
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
[versions]
|
||||
kotlin = "2.0.20"
|
||||
ksp = "2.0.20-1.0.25"
|
||||
kotlin = "2.2.10"
|
||||
ksp = "2.2.10-2.0.2"
|
||||
coroutines = "1.10.2"
|
||||
junit = "5.10.1"
|
||||
okhttp = "4.12.0"
|
||||
okio = "3.11.0"
|
||||
okhttp = "5.1.0"
|
||||
okio = "3.16.0"
|
||||
json = "20240303"
|
||||
androidx-collection = "1.5.0"
|
||||
jsoup = "1.19.1"
|
||||
jsoup = "1.21.2"
|
||||
quickjs = "1.1.0"
|
||||
|
||||
[plugins]
|
||||
|
||||
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,7 +1,7 @@
|
||||
#Wed Aug 27 01:56:37 ICT 2025
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionSha256Sum=20f1b1176237254a6fc204d8434196fa11a4cfb387567519c61556e8710aed78
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
||||
distributionSha256Sum=bd71102213493060956ec229d946beee57158dbd89d0e62b91bca0fa2c5f3531
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -181,7 +181,7 @@ class ParserProcessor(
|
||||
}
|
||||
val localeComment = localeTitle?.toTitleCase(localeObj)?.let { " /* $it */" }.orEmpty()
|
||||
sourcesWriter?.write(
|
||||
"\t$deprecationString$name(\"$title\", $localeString$localeComment, ContentType.$type, $isBroken),\n",
|
||||
"\t$deprecationString$name(\"$title\", $localeString$localeComment, $type, $isBroken),\n",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,5 +15,4 @@ public object ErrorMessages {
|
||||
public const val FILTER_BOTH_STATES_GENRES_NOT_SUPPORTED: String =
|
||||
"Filtering by both genres and states is not supported by this source"
|
||||
public const val SEARCH_NOT_SUPPORTED: String = "Search is not supported by this source"
|
||||
public const val RESPONSE_NULL_BODY: String = "Response has no body"
|
||||
}
|
||||
|
||||
@@ -19,11 +19,11 @@ public object CloudFlareHelper {
|
||||
if (response.code != HTTP_FORBIDDEN && response.code != HTTP_UNAVAILABLE) {
|
||||
return PROTECTION_NOT_DETECTED
|
||||
}
|
||||
val content = if (response.body != null) {
|
||||
val content = try {
|
||||
response.peekBody(Long.MAX_VALUE).use {
|
||||
Jsoup.parse(it.byteStream(), Charsets.UTF_8.name(), response.request.url.toString())
|
||||
}
|
||||
} else {
|
||||
} catch (_: IllegalStateException) {
|
||||
return PROTECTION_NOT_DETECTED
|
||||
}
|
||||
return when {
|
||||
|
||||
@@ -7,7 +7,6 @@ import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import okhttp3.internal.closeQuietly
|
||||
import okhttp3.internal.headersContentLength
|
||||
import org.jsoup.internal.StringUtil
|
||||
import org.jsoup.nodes.Element
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
@@ -28,6 +27,7 @@ import java.util.concurrent.TimeUnit
|
||||
private const val DOMAIN_UNAUTHORIZED = "e-hentai.org"
|
||||
private const val DOMAIN_AUTHORIZED = "exhentai.org"
|
||||
private val TAG_PREFIXES = arrayOf("male:", "female:", "other:")
|
||||
private const val BANNED_RESPONSE_LENGTH = 256L
|
||||
|
||||
@MangaSourceParser("EXHENTAI", "ExHentai", type = ContentType.HENTAI)
|
||||
internal class ExHentaiParser(
|
||||
@@ -301,8 +301,8 @@ internal class ExHentaiParser(
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val response = chain.proceed(chain.request())
|
||||
if (response.headersContentLength() <= 256) {
|
||||
val text = response.peekBody(256).use { it.string() }
|
||||
if (response.headersContentLength(BANNED_RESPONSE_LENGTH) <= BANNED_RESPONSE_LENGTH) {
|
||||
val text = response.peekBody(BANNED_RESPONSE_LENGTH).use { it.string() }
|
||||
if (text.contains("IP address has been temporarily banned", ignoreCase = true)) {
|
||||
val hours = Regex("([0-9]+) hours?").find(text)?.groupValues?.getOrNull(1)?.toLongOrNull() ?: 0
|
||||
val minutes = Regex("([0-9]+) minutes?").find(text)?.groupValues?.getOrNull(1)?.toLongOrNull() ?: 0
|
||||
|
||||
@@ -19,6 +19,7 @@ import org.koitharu.kotatsu.parsers.model.MangaTag
|
||||
import org.koitharu.kotatsu.parsers.model.RATING_UNKNOWN
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.parsers.util.attrAsRelativeUrl
|
||||
import org.koitharu.kotatsu.parsers.util.attrOrNull
|
||||
import org.koitharu.kotatsu.parsers.util.generateUid
|
||||
import org.koitharu.kotatsu.parsers.util.mapChapters
|
||||
import org.koitharu.kotatsu.parsers.util.mapNotNullToSet
|
||||
@@ -28,6 +29,7 @@ import org.koitharu.kotatsu.parsers.util.parseFailed
|
||||
import org.koitharu.kotatsu.parsers.util.parseHtml
|
||||
import org.koitharu.kotatsu.parsers.util.selectFirstOrThrow
|
||||
import org.koitharu.kotatsu.parsers.util.src
|
||||
import org.koitharu.kotatsu.parsers.util.textOrNull
|
||||
import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl
|
||||
import org.koitharu.kotatsu.parsers.util.toTitleCase
|
||||
import org.koitharu.kotatsu.parsers.util.urlEncoded
|
||||
@@ -255,8 +257,8 @@ internal class KdtScans(context: MangaLoaderContext) :
|
||||
private suspend fun fetchAvailableTags(): Set<MangaTag> {
|
||||
val doc = webClient.httpGet("https://$domain/manga/").parseHtml()
|
||||
return doc.select("ul.genrez li").mapNotNullToSet { li ->
|
||||
val key = li.selectFirst("input").attr("value") ?: return@mapNotNullToSet null
|
||||
val title = li.selectFirst("label").text().toTitleCase()
|
||||
val key = li.selectFirst("input")?.attrOrNull("value") ?: return@mapNotNullToSet null
|
||||
val title = li.selectFirst("label")?.textOrNull()?.toTitleCase() ?: return@mapNotNullToSet null
|
||||
MangaTag(
|
||||
key = key,
|
||||
title = title,
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
package org.koitharu.kotatsu.parsers.site.es
|
||||
|
||||
import org.koitharu.kotatsu.parsers.Broken
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||
import org.koitharu.kotatsu.parsers.core.PagedMangaParser
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import org.koitharu.kotatsu.parsers.Broken
|
||||
import java.util.*
|
||||
|
||||
@Broken // Website closed
|
||||
@MangaSourceParser("DRAGONTRANSLATION", "Dragon Translation", "es")
|
||||
internal class DragonTranslationParser(context: MangaLoaderContext) : PagedMangaParser(context, MangaParserSource.DRAGONTRANSLATION, 30) {
|
||||
internal class DragonTranslationParser(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaParserSource.DRAGONTRANSLATION, 30) {
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("dragontranslation.net")
|
||||
|
||||
@@ -60,12 +61,14 @@ internal class DragonTranslationParser(context: MangaLoaderContext) : PagedManga
|
||||
|
||||
if (filter.types.isNotEmpty()) {
|
||||
append("&type=")
|
||||
append(
|
||||
when (filter.types.oneOrThrowIfMany()) {
|
||||
ContentType.MANGA -> "manga"
|
||||
ContentType.MANHWA -> "manhwa"
|
||||
ContentType.MANHUA -> "manhua"
|
||||
else -> ""
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -81,7 +84,7 @@ internal class DragonTranslationParser(context: MangaLoaderContext) : PagedManga
|
||||
url = href,
|
||||
publicUrl = href,
|
||||
coverUrl = coverUrl,
|
||||
title = div.selectFirst("h2.card-title.fs-6.entry-title").text(),
|
||||
title = div.selectFirst("h2.card-title.fs-6.entry-title")?.text().orEmpty(),
|
||||
altTitles = emptySet(),
|
||||
rating = RATING_UNKNOWN,
|
||||
tags = emptySet(),
|
||||
@@ -150,24 +153,31 @@ internal class DragonTranslationParser(context: MangaLoaderContext) : PagedManga
|
||||
dateText.contains("minutos") -> {
|
||||
now - (number * 60 * 1000L)
|
||||
}
|
||||
|
||||
dateText.contains("horas") -> {
|
||||
now - (number * 60 * 60 * 1000L)
|
||||
}
|
||||
|
||||
dateText.contains("días") -> {
|
||||
now - (number * 24 * 60 * 60 * 1000L)
|
||||
}
|
||||
|
||||
dateText.contains("día") -> {
|
||||
now - (number * 24 * 60 * 60 * 1000L)
|
||||
}
|
||||
|
||||
dateText.contains("semanas") -> {
|
||||
now - (number * 7 * 24 * 60 * 60 * 1000L)
|
||||
}
|
||||
|
||||
dateText.contains("meses") -> {
|
||||
now - (number * 30 * 24 * 60 * 60 * 1000L)
|
||||
}
|
||||
|
||||
dateText.contains("años") -> {
|
||||
now - (number * 365 * 24 * 60 * 60 * 1000L)
|
||||
}
|
||||
|
||||
else -> 0L
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ internal class LuratoonScansParser(context: MangaLoaderContext) :
|
||||
val response = chain.proceed(chain.request())
|
||||
if (response.mimeType == "application/octet-stream") {
|
||||
val (bytes, name) = response.use { resp ->
|
||||
ZipInputStream(resp.requireBody().byteStream()).use {
|
||||
ZipInputStream(resp.body.byteStream()).use {
|
||||
val entry = it.nextEntry
|
||||
it.readBytes() to entry?.name
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import okhttp3.internal.closeQuietly
|
||||
import okhttp3.internal.headersContentLength
|
||||
import okio.IOException
|
||||
import org.json.JSONArray
|
||||
import org.jsoup.nodes.Element
|
||||
@@ -102,9 +101,10 @@ internal abstract class GroupleParser(
|
||||
} else {
|
||||
advancedSearch(offset, order, filter).parseHtml()
|
||||
}
|
||||
checkNotNull(root) { "Root not found" }
|
||||
val tiles = root.selectFirst("div.tiles.row")
|
||||
if (tiles == null) {
|
||||
if (!root.getElementsContainingOwnText(NOTHING_FOUND).isNullOrEmpty()) {
|
||||
if (root.getElementsContainingOwnText(NOTHING_FOUND).isNotEmpty()) {
|
||||
return emptyList()
|
||||
}
|
||||
root.parseFailed("No tiles found")
|
||||
@@ -180,7 +180,7 @@ internal abstract class GroupleParser(
|
||||
if (translations.isNullOrEmpty() || a.attr("data-translations").isEmpty()) {
|
||||
var translators = ""
|
||||
val translatorElement = a.attr("title")
|
||||
if (!translatorElement.isNullOrBlank()) {
|
||||
if (translatorElement.isNotBlank()) {
|
||||
translators = translatorElement.replace("(Переводчик),", "&").removeSuffix(" (Переводчик)")
|
||||
}
|
||||
listOf(
|
||||
|
||||
@@ -4,6 +4,7 @@ package org.koitharu.kotatsu.parsers.util
|
||||
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import okhttp3.*
|
||||
import okhttp3.internal.toLongOrDefault
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
|
||||
@@ -42,29 +43,13 @@ public inline fun Response.map(mapper: (ResponseBody) -> ResponseBody): Response
|
||||
contract {
|
||||
callsInPlace(mapper, InvocationKind.AT_MOST_ONCE)
|
||||
}
|
||||
return body?.use { responseBody ->
|
||||
return body.use { responseBody ->
|
||||
newBuilder()
|
||||
.body(mapper(responseBody))
|
||||
.build()
|
||||
} ?: this
|
||||
}
|
||||
}
|
||||
|
||||
public fun Cookie.newBuilder(): Cookie.Builder = Cookie.Builder().also { c ->
|
||||
c.name(name)
|
||||
c.value(value)
|
||||
if (persistent) {
|
||||
c.expiresAt(expiresAt)
|
||||
}
|
||||
if (hostOnly) {
|
||||
c.hostOnlyDomain(domain)
|
||||
} else {
|
||||
c.domain(domain)
|
||||
}
|
||||
c.path(path)
|
||||
if (secure) {
|
||||
c.secure()
|
||||
}
|
||||
if (httpOnly) {
|
||||
c.httpOnly()
|
||||
}
|
||||
}
|
||||
public fun Response.headersContentLength(
|
||||
defaultValue: Long = -1,
|
||||
): Long = headers["Content-Length"]?.toLongOrDefault(defaultValue) ?: defaultValue
|
||||
|
||||
@@ -8,7 +8,6 @@ import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Document
|
||||
import org.koitharu.kotatsu.parsers.ErrorMessages
|
||||
import org.koitharu.kotatsu.parsers.InternalParsersApi
|
||||
import java.text.DateFormat
|
||||
|
||||
@@ -22,7 +21,7 @@ internal const val SCHEME_HTTPS = "https"
|
||||
*/
|
||||
// TODO suspend
|
||||
public fun Response.parseHtml(): Document = use { response ->
|
||||
val body = response.requireBody()
|
||||
val body = response.body
|
||||
val charset = body.contentType()?.charset()?.name()
|
||||
Jsoup.parse(body.byteStream(), charset, response.request.url.toString())
|
||||
}
|
||||
@@ -33,7 +32,7 @@ public fun Response.parseHtml(): Document = use { response ->
|
||||
* @see [parseHtml]
|
||||
*/
|
||||
public fun Response.parseJson(): JSONObject = use { response ->
|
||||
JSONObject(response.requireBody().string())
|
||||
JSONObject(response.body.string())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,15 +41,15 @@ public fun Response.parseJson(): JSONObject = use { response ->
|
||||
* @see [parseHtml]
|
||||
*/
|
||||
public fun Response.parseJsonArray(): JSONArray = use { response ->
|
||||
JSONArray(response.requireBody().string())
|
||||
JSONArray(response.body.string())
|
||||
}
|
||||
|
||||
public fun Response.parseRaw(): String = use { response ->
|
||||
response.requireBody().string()
|
||||
response.body.string()
|
||||
}
|
||||
|
||||
public fun Response.parseBytes(): ByteArray = use { response ->
|
||||
response.requireBody().bytes()
|
||||
response.body.bytes()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,6 +98,5 @@ public fun DateFormat.parseSafe(str: String?): Long = if (str.isNullOrEmpty()) {
|
||||
}.getOrDefault(0L)
|
||||
}
|
||||
|
||||
public fun Response.requireBody(): ResponseBody = requireNotNull(body) {
|
||||
ErrorMessages.RESPONSE_NULL_BODY
|
||||
}
|
||||
@Deprecated("Useless since OkHttp 5.0", replaceWith = ReplaceWith("body"))
|
||||
public fun Response.requireBody(): ResponseBody = body
|
||||
|
||||
@@ -3,10 +3,8 @@ package org.koitharu.kotatsu.parsers.util
|
||||
import kotlinx.coroutines.*
|
||||
import org.koitharu.kotatsu.parsers.MangaParser
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaListFilter
|
||||
import org.koitharu.kotatsu.parsers.model.SortOrder
|
||||
import org.koitharu.kotatsu.parsers.model.search.MangaSearchQuery
|
||||
import org.koitharu.kotatsu.parsers.model.search.QueryCriteria
|
||||
import org.koitharu.kotatsu.parsers.model.search.SearchableField
|
||||
|
||||
public class RelatedMangaFinder(
|
||||
private val parsers: Collection<MangaParser>,
|
||||
@@ -36,10 +34,15 @@ public class RelatedMangaFinder(
|
||||
val results = words.map { keyword ->
|
||||
scope.async {
|
||||
val result = parser.getList(
|
||||
MangaSearchQuery.Builder()
|
||||
.order(SortOrder.RELEVANCE)
|
||||
.criterion(QueryCriteria.Match(SearchableField.TITLE_NAME, keyword))
|
||||
.build(),
|
||||
0,
|
||||
if (SortOrder.RELEVANCE in parser.availableSortOrders) {
|
||||
SortOrder.RELEVANCE
|
||||
} else {
|
||||
parser.availableSortOrders.first()
|
||||
},
|
||||
MangaListFilter(
|
||||
query = keyword,
|
||||
),
|
||||
)
|
||||
result.filter { it.id != seed.id && it.containKeyword(keyword) }
|
||||
}
|
||||
@@ -48,6 +51,7 @@ public class RelatedMangaFinder(
|
||||
}
|
||||
|
||||
private fun Manga.containKeyword(keyword: String): Boolean {
|
||||
return title.contains(keyword, ignoreCase = true) || altTitle?.contains(keyword, ignoreCase = true) == true
|
||||
return title.contains(keyword, ignoreCase = true)
|
||||
|| altTitles.any { it.contains(keyword, ignoreCase = true) }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user