Fixes batch

master
Koitharu 2 years ago
parent 2f5441ef20
commit 789e39b6cb
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -71,4 +71,15 @@ class MangaChapter(
override fun toString(): String { override fun toString(): String {
return "MangaChapter($id - #$number [$url] - $source)" return "MangaChapter($id - #$number [$url] - $source)"
} }
internal fun copy(number: Int) = MangaChapter(
id = id,
name = name,
number = number,
url = url,
scanlator = scanlator,
uploadDate = uploadDate,
branch = branch,
source = source,
)
} }

@ -417,6 +417,4 @@ internal class ExHentaiParser(
} }
return joiner.complete().takeUnless { it.isEmpty() } return joiner.complete().takeUnless { it.isEmpty() }
} }
private fun String.isNumeric() = all { c -> c.isDigit() }
} }

@ -155,28 +155,13 @@ internal abstract class GroupleParser(
val scripts = doc.select("script") val scripts = doc.select("script")
for (script in scripts) { for (script in scripts) {
val data = script.html() val data = script.html()
val pos = data.indexOf("rm_h.readerInit( 0,") var pos = data.indexOf("rm_h.readerDoInit(")
if (pos == -1) { if (pos != -1) {
continue parsePagesNew(data, pos)?.let { return it } ?: continue
} }
val json = data.substring(pos).substringAfter('(').substringBefore('\n').substringBeforeLast(')') pos = data.indexOf("rm_h.readerInit( 0,")
if (json.isEmpty()) { if (pos != -1) {
continue parsePagesOld(data, pos)?.let { return it } ?: continue
}
val ja = JSONArray("[$json]")
val pages = ja.getJSONArray(1)
val servers = ja.getJSONArray(3).mapJSON { it.getString("path") }
val serversStr = servers.joinToString("|")
return (0 until pages.length()).map { i ->
val page = pages.getJSONArray(i)
val primaryServer = page.getString(0)
val url = page.getString(2)
MangaPage(
id = generateUid(url),
url = "$primaryServer|$serversStr|$url",
preview = null,
source = source,
)
} }
} }
doc.parseFailed("Pages list not found at ${chapter.url}") doc.parseFailed("Pages list not found at ${chapter.url}")
@ -373,4 +358,48 @@ internal abstract class GroupleParser(
source = source, source = source,
) )
} }
private fun parsePagesNew(data: String, pos: Int): List<MangaPage>? {
val json = data.substring(pos).substringAfter('(').substringBefore('\n').substringBeforeLast(')')
if (json.isEmpty()) {
return null
}
val ja = JSONArray("[$json]")
val pages = ja.getJSONArray(0)
val servers = ja.getJSONArray(2).mapJSON { it.getString("path") }
val serversStr = servers.joinToString("|")
return (0 until pages.length()).map { i ->
val page = pages.getJSONArray(i)
val primaryServer = page.getString(2)
val url = page.getString(1)
MangaPage(
id = generateUid(url),
url = "$primaryServer|$serversStr|$url",
preview = null,
source = source,
)
}
}
private fun parsePagesOld(data: String, pos: Int): List<MangaPage>? {
val json = data.substring(pos).substringAfter('(').substringBefore('\n').substringBeforeLast(')')
if (json.isEmpty()) {
return null
}
val ja = JSONArray("[$json]")
val pages = ja.getJSONArray(1)
val servers = ja.getJSONArray(3).mapJSON { it.getString("path") }
val serversStr = servers.joinToString("|")
return (0 until pages.length()).map { i ->
val page = pages.getJSONArray(i)
val primaryServer = page.getString(0)
val url = page.getString(2)
MangaPage(
id = generateUid(url),
url = "$primaryServer|$serversStr|$url",
preview = null,
source = source,
)
}
}
} }

@ -11,9 +11,8 @@ internal class MintMangaParser(
) : GroupleParser(context, MangaSource.MINTMANGA, 2) { ) : GroupleParser(context, MangaSource.MINTMANGA, 2) {
override val configKeyDomain = ConfigKey.Domain( override val configKeyDomain = ConfigKey.Domain(
"23.mintmanga.live", "24.mintmanga.one",
"mintmanga.live", "mintmanga.live",
"mintmanga.com", "mintmanga.com",
"m.mintmanga.live",
) )
} }

@ -1,6 +1,7 @@
package org.koitharu.kotatsu.parsers.site.ru.multichan package org.koitharu.kotatsu.parsers.site.ru.multichan
import okhttp3.HttpUrl import okhttp3.HttpUrl
import org.jsoup.internal.StringUtil
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaParser import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
@ -22,21 +23,17 @@ internal abstract class ChanParser(
SortOrder.RATING, SortOrder.RATING,
) )
override val isTagsExclusionSupported: Boolean = true
override val authUrl: String override val authUrl: String
get() = "https://${domain}" get() = "https://${domain}"
override val isAuthorized: Boolean override val isAuthorized: Boolean
get() = context.cookieJar.getCookies(domain).any { it.name == "dle_user_id" } get() = context.cookieJar.getCookies(domain).any { it.name == "dle_user_id" }
override suspend fun getList( override suspend fun getList(offset: Int, filter: MangaListFilter?): List<Manga> {
offset: Int,
query: String?,
tags: Set<MangaTag>?,
tagsExclude: Set<MangaTag>?,
sortOrder: SortOrder,
): List<Manga> {
val domain = domain val domain = domain
val doc = webClient.httpGet(buildUrl(offset, query, tags, sortOrder)).parseHtml() val doc = webClient.httpGet(buildUrl(offset, filter)).parseHtml()
val root = doc.body().selectFirst("div.main_fon")?.getElementById("content") val root = doc.body().selectFirst("div.main_fon")?.getElementById("content")
?: doc.parseFailed("Cannot find root") ?: doc.parseFailed("Cannot find root")
return root.select("div.content_row").mapNotNull { row -> return root.select("div.content_row").mapNotNull { row ->
@ -180,48 +177,56 @@ internal abstract class ChanParser(
protected open fun buildUrl( protected open fun buildUrl(
offset: Int, offset: Int,
query: String?, filter: MangaListFilter?,
tags: Set<MangaTag>?,
sortOrder: SortOrder,
): HttpUrl { ): HttpUrl {
val builder = urlBuilder() val builder = urlBuilder()
builder.addQueryParameter("offset", offset.toString()) builder.addQueryParameter("offset", offset.toString())
when { when (filter) {
!query.isNullOrEmpty() -> { is MangaListFilter.Search -> {
builder.addQueryParameter("do", "search") builder.addQueryParameter("do", "search")
builder.addQueryParameter("subaction", "search") builder.addQueryParameter("subaction", "search")
builder.addQueryParameter("search_start", ((offset / 40) + 1).toString()) builder.addQueryParameter("search_start", ((offset / 40) + 1).toString())
builder.addQueryParameter("full_search", "0") builder.addQueryParameter("full_search", "0")
builder.addQueryParameter("result_from", (offset + 1).toString()) builder.addQueryParameter("result_from", (offset + 1).toString())
builder.addQueryParameter("result_num", "40") builder.addQueryParameter("result_num", "40")
builder.addQueryParameter("story", query) builder.addQueryParameter("story", filter.query)
builder.addQueryParameter("need_sort_date", "false") builder.addQueryParameter("need_sort_date", "false")
} }
!tags.isNullOrEmpty() -> { is MangaListFilter.Advanced -> {
builder.addPathSegment("tags") if (filter.tags.isNotEmpty() || filter.tagsExclude.isNotEmpty()) {
builder.addPathSegment(tags.joinToString("+") { it.key }) builder.addPathSegment("tags")
builder.addQueryParameter( val joiner = StringUtil.StringJoiner("+")
"n", filter.tags.forEach { joiner.add(it.key) }
when (sortOrder) { filter.tagsExclude.forEach { joiner.add("-"); joiner.append(it.key) }
SortOrder.RATING, builder.addPathSegment(joiner.complete())
SortOrder.POPULARITY, builder.addQueryParameter(
-> "favdesc" "n",
when (filter.sortOrder) {
SortOrder.ALPHABETICAL -> "abcasc" SortOrder.RATING,
else -> "" // SortOrder.NEWEST SortOrder.POPULARITY,
}, -> "favdesc"
)
SortOrder.ALPHABETICAL -> "abcasc"
else -> "" // SortOrder.NEWEST
},
)
} else {
when (filter.sortOrder) {
SortOrder.POPULARITY -> builder.addPathSegment("mostviews")
SortOrder.ALPHABETICAL -> builder.addPathSegment("catalog")
SortOrder.RATING -> builder.addPathSegment("mostfavorites")
else -> { // SortOrder.NEWEST
builder.addPathSegment("manga")
builder.addPathSegment("new")
}
}
}
} }
else -> when (sortOrder) { null -> {
SortOrder.POPULARITY -> builder.addPathSegment("mostviews") builder.addPathSegment("manga")
SortOrder.ALPHABETICAL -> builder.addPathSegment("catalog") builder.addPathSegment("new")
SortOrder.RATING -> builder.addPathSegment("mostfavorites")
else -> { // SortOrder.NEWEST
builder.addPathSegment("manga")
builder.addPathSegment("new")
}
} }
} }
return builder.build() return builder.build()

@ -58,10 +58,10 @@ internal class HenChanParser(context: MangaLoaderContext) : ChanParser(context,
) )
} }
override fun buildUrl(offset: Int, query: String?, tags: Set<MangaTag>?, sortOrder: SortOrder): HttpUrl { override fun buildUrl(offset: Int, filter: MangaListFilter?): HttpUrl = when {
if (query.isNullOrEmpty() && tags.isNullOrEmpty()) { filter is MangaListFilter.Advanced && filter.tags.isEmpty() && filter.tagsExclude.isEmpty() -> {
val builder = urlBuilder().addQueryParameter("offset", offset.toString()) val builder = urlBuilder().addQueryParameter("offset", offset.toString())
when (sortOrder) { when (filter.sortOrder) {
SortOrder.POPULARITY -> { SortOrder.POPULARITY -> {
builder.addPathSegment("mostviews") builder.addPathSegment("mostviews")
builder.addQueryParameter("sort", "manga") builder.addQueryParameter("sort", "manga")
@ -77,8 +77,18 @@ internal class HenChanParser(context: MangaLoaderContext) : ChanParser(context,
builder.addPathSegment("newest") builder.addPathSegment("newest")
} }
} }
return builder.build() builder.build()
}
filter == null -> {
val builder = urlBuilder().addQueryParameter("offset", offset.toString())
builder.addPathSegment("manga")
builder.addPathSegment("newest")
builder.build()
}
else -> {
super.buildUrl(offset, filter)
} }
return super.buildUrl(offset, query, tags, sortOrder)
} }
} }

@ -178,9 +178,11 @@ internal abstract class WpComicsParser(
val tagItems = doc.select("div.genre-item") val tagItems = doc.select("div.genre-item")
val result = ArrayMap<String, MangaTag>(tagItems.size) val result = ArrayMap<String, MangaTag>(tagItems.size)
for (item in tagItems) { for (item in tagItems) {
val title = item.text().trim() val title = item.text()
val key = item.select("span[data-id]").attr("data-id") val key = item.select("span[data-id]").attr("data-id")
result[title] = MangaTag(title = title, key = key, source = source) if (key.isNotEmpty() && title.isNotEmpty()) {
result[title] = MangaTag(title = title, key = key, source = source)
}
} }
tagCache = result tagCache = result
result result
@ -216,7 +218,7 @@ internal abstract class WpComicsParser(
protected open val selectDate = "div.col-xs-4" protected open val selectDate = "div.col-xs-4"
protected open val selectChapter = "div#nt_listchapter li:not(.heading)" protected open val selectChapter = "div#nt_listchapter li .chapter"
protected open suspend fun getChapters(doc: Document): List<MangaChapter> { protected open suspend fun getChapters(doc: Document): List<MangaChapter> {
return doc.body().select(selectChapter).mapChapters(reversed = true) { i, li -> return doc.body().select(selectChapter).mapChapters(reversed = true) { i, li ->

@ -1,13 +1,15 @@
package org.koitharu.kotatsu.parsers.site.wpcomics.en package org.koitharu.kotatsu.parsers.site.wpcomics.en
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import org.jsoup.nodes.Document
import org.koitharu.kotatsu.parsers.MangaLoaderContext import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.model.* import org.koitharu.kotatsu.parsers.model.*
import org.koitharu.kotatsu.parsers.site.wpcomics.WpComicsParser import org.koitharu.kotatsu.parsers.site.wpcomics.WpComicsParser
import org.koitharu.kotatsu.parsers.util.* import org.koitharu.kotatsu.parsers.util.*
import java.util.EnumSet import java.util.*
@MangaSourceParser("XOXOCOMICS", "XoxoComics", "en", ContentType.COMICS) @MangaSourceParser("XOXOCOMICS", "XoxoComics", "en", ContentType.COMICS)
internal class XoxoComics(context: MangaLoaderContext) : internal class XoxoComics(context: MangaLoaderContext) :
@ -146,6 +148,29 @@ internal class XoxoComics(context: MangaLoaderContext) :
) )
} }
override suspend fun getChapters(doc: Document): List<MangaChapter> {
val pages = doc.select("ul.pagination > li:not(.active)")
return if (pages.size <= 1) {
super.getChapters(doc)
} else {
val list = coroutineScope {
pages.mapNotNull { page ->
val a = page.selectFirst("a") ?: return@mapNotNull null
if (a.text().isNumeric()) {
val href = a.attrAsAbsoluteUrl("href")
async {
super.getChapters(webClient.httpGet(href).parseHtml()).asReversed()
}
} else {
null // TODO support pagination with overflow
}
}.awaitAll().flattenTo(ArrayList())
}
list.addAll(super.getChapters(doc).asReversed())
list.reverse()
list.mapIndexed { i, x -> x.copy(number = i + 1) }
}
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> { override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val fullUrl = chapter.url.toAbsoluteUrl(domain) + "/all" val fullUrl = chapter.url.toAbsoluteUrl(domain) + "/all"

@ -10,6 +10,7 @@ import org.koitharu.kotatsu.parsers.site.wpcomics.WpComicsParser
internal class NetTruyen(context: MangaLoaderContext) : internal class NetTruyen(context: MangaLoaderContext) :
WpComicsParser(context, MangaSource.NETTRUYEN, "www.nettruyenlive.com", 36) { WpComicsParser(context, MangaSource.NETTRUYEN, "www.nettruyenlive.com", 36) {
override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain( override val configKeyDomain: ConfigKey.Domain = ConfigKey.Domain(
"www.nettruyenaz.com",
"www.nettruyenlive.com", "www.nettruyenlive.com",
"www.nettruyenio.com", "www.nettruyenio.com",
"www.nettruyento.com", "www.nettruyento.com",

@ -7,4 +7,4 @@ import org.koitharu.kotatsu.parsers.site.wpcomics.WpComicsParser
@MangaSourceParser("NHATTRUYENMIN", "NhattruyenPlus", "vi") @MangaSourceParser("NHATTRUYENMIN", "NhattruyenPlus", "vi")
internal class Nhattruyenmin(context: MangaLoaderContext) : internal class Nhattruyenmin(context: MangaLoaderContext) :
WpComicsParser(context, MangaSource.NHATTRUYENMIN, "nhattruyenplus.com") WpComicsParser(context, MangaSource.NHATTRUYENMIN, "nhattruyenmax.com")

@ -240,3 +240,5 @@ inline fun <T> Appendable.appendAll(
append(transform(item)) append(transform(item))
} }
} }
fun String.isNumeric() = all { c -> c.isDigit() }

Loading…
Cancel
Save