Update dependencies

master
Koitharu 8 months ago
parent 4a854c7a23
commit 19567f9642
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -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]

@ -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) }
}
}

Loading…
Cancel
Save