Use relative urls for mangas and change id generation algorythm

This commit is contained in:
Koitharu
2021-01-30 18:50:10 +02:00
parent c64115a268
commit 4aa1b58109
18 changed files with 235 additions and 223 deletions

View File

@@ -1,6 +1,5 @@
package org.koitharu.kotatsu.core.parser
import okhttp3.HttpUrl.Companion.toHttpUrl
import org.koitharu.kotatsu.base.domain.MangaLoaderContext
import org.koitharu.kotatsu.core.model.MangaPage
import org.koitharu.kotatsu.core.model.MangaSource
@@ -13,27 +12,60 @@ abstract class RemoteMangaRepository(
protected abstract val source: MangaSource
protected val conf by lazy {
protected abstract val defaultDomain: String
private val conf by lazy {
loaderContext.getSettings(source)
}
override val sortOrders: Set<SortOrder> get() = emptySet()
override suspend fun getPageUrl(page: MangaPage): String = page.url
override suspend fun getPageUrl(page: MangaPage): String = page.url.withDomain()
override suspend fun getTags(): Set<MangaTag> = emptySet()
abstract fun onCreatePreferences(): Set<String>
protected fun generateUid(url: String): Long {
val uri = url.toHttpUrl()
val x = source.name.hashCode()
val y = "${uri.encodedPath}?${uri.query}".hashCode()
return (x.toLong() shl 32) or (y.toLong() and 0xffffffffL)
protected fun getDomain() = conf.getDomain(defaultDomain)
protected fun String.withDomain() = when {
this.startsWith("//") -> buildString {
append("http")
if (conf.isUseSsl(true)) {
append('s')
}
append(":")
append(this@withDomain)
}
this.startsWith("/") -> buildString {
append("http")
if (conf.isUseSsl(true)) {
append('s')
}
append("://")
append(conf.getDomain(defaultDomain))
append(this@withDomain)
}
else -> this
}
protected fun generateUid(id: Int): Long {
val x = source.name.hashCode()
return (x.toLong() shl 32) or (id.toLong() and 0xffffffffL)
protected fun generateUid(url: String): Long {
var h = 1125899906842597L
source.name.forEach { c ->
h = 31 * h + c.toLong()
}
url.forEach { c ->
h = 31 * h + c.toLong()
}
return h
}
protected fun generateUid(id: Long): Long {
var h = 1125899906842597L
source.name.forEach { c ->
h = 31 * h + c.toLong()
}
h = 31 * h + id
return h
}
}

View File

@@ -13,8 +13,6 @@ abstract class ChanRepository(loaderContext: MangaLoaderContext) : RemoteMangaRe
loaderContext
) {
protected abstract val defaultDomain: String
override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.NEWEST,
SortOrder.POPULARITY,
@@ -27,7 +25,7 @@ abstract class ChanRepository(loaderContext: MangaLoaderContext) : RemoteMangaRe
sortOrder: SortOrder?,
tag: MangaTag?
): List<Manga> {
val domain = conf.getDomain(defaultDomain)
val domain = getDomain()
val url = when {
!query.isNullOrEmpty() -> {
if (offset != 0) {
@@ -44,9 +42,9 @@ abstract class ChanRepository(loaderContext: MangaLoaderContext) : RemoteMangaRe
return root.select("div.content_row").mapNotNull { row ->
val a = row.selectFirst("div.manga_row1")?.selectFirst("h2")?.selectFirst("a")
?: return@mapNotNull null
val href = a.attr("href").withDomain(domain)
val href = a.relUrl("href")
Manga(
id = href.longHashCode(),
id = generateUid(href),
url = href,
altTitle = a.attr("title"),
title = a.text().substringAfterLast('(').substringBeforeLast(')'),
@@ -55,7 +53,7 @@ abstract class ChanRepository(loaderContext: MangaLoaderContext) : RemoteMangaRe
"/mangaka"
).firstOrNull()?.text(),
coverUrl = row.selectFirst("div.manga_images")?.selectFirst("img")
?.attr("src")?.withDomain(domain).orEmpty(),
?.absUrl("src").orEmpty(),
tags = runCatching {
row.selectFirst("div.genre")?.select("a")?.mapToSet {
MangaTag(
@@ -72,20 +70,18 @@ abstract class ChanRepository(loaderContext: MangaLoaderContext) : RemoteMangaRe
override suspend fun getDetails(manga: Manga): Manga {
val domain = conf.getDomain(defaultDomain)
val doc = loaderContext.httpGet(manga.url).parseHtml()
val doc = loaderContext.httpGet(manga.url.withDomain()).parseHtml()
val root =
doc.body().getElementById("dle-content") ?: throw ParseException("Cannot find root")
return manga.copy(
description = root.getElementById("description")?.html()?.substringBeforeLast("<div"),
largeCoverUrl = root.getElementById("cover")?.attr("src")?.withDomain(domain),
largeCoverUrl = root.getElementById("cover")?.absUrl("src"),
chapters = root.select("table.table_cha").flatMap { table ->
table.select("div.manga2")
}.mapNotNull { it.selectFirst("a") }.reversed().mapIndexedNotNull { i, a ->
val href = a.attr("href")
?.withDomain(domain) ?: return@mapIndexedNotNull null
}.map { it.selectFirst("a") }.reversed().mapIndexedNotNull { i, a ->
val href = a.relUrl("href")
MangaChapter(
id = href.longHashCode(),
id = generateUid(href),
name = a.text().trim(),
number = i + 1,
url = href,
@@ -96,7 +92,8 @@ abstract class ChanRepository(loaderContext: MangaLoaderContext) : RemoteMangaRe
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val doc = loaderContext.httpGet(chapter.url).parseHtml()
val fullUrl = chapter.url.withDomain()
val doc = loaderContext.httpGet(fullUrl).parseHtml()
val scripts = doc.select("script")
for (script in scripts) {
val data = script.html()
@@ -106,13 +103,17 @@ abstract class ChanRepository(loaderContext: MangaLoaderContext) : RemoteMangaRe
}
val json = data.substring(pos).substringAfter('[').substringBefore(';')
.substringBeforeLast(']')
val domain = getDomain()
return json.split(",").mapNotNull {
it.trim().removeSurrounding('"', '\'').takeUnless(String::isBlank)
it.trim()
.removeSurrounding('"', '\'')
.toRelativeUrl(domain)
.takeUnless(String::isBlank)
}.map { url ->
MangaPage(
id = url.longHashCode(),
id = generateUid(url),
url = url,
referer = chapter.url,
referer = fullUrl,
source = source
)
}
@@ -121,7 +122,7 @@ abstract class ChanRepository(loaderContext: MangaLoaderContext) : RemoteMangaRe
}
override suspend fun getTags(): Set<MangaTag> {
val domain = conf.getDomain(defaultDomain)
val domain = getDomain()
val doc = loaderContext.httpGet("https://$domain/catalog").parseHtml()
val root = doc.body().selectFirst("div.main_fon").getElementById("side")
.select("ul").last()

View File

@@ -14,6 +14,8 @@ class DesuMeRepository(loaderContext: MangaLoaderContext) : RemoteMangaRepositor
override val source = MangaSource.DESUME
override val defaultDomain = "desu.me"
override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.UPDATED,
SortOrder.POPULARITY,
@@ -27,7 +29,7 @@ class DesuMeRepository(loaderContext: MangaLoaderContext) : RemoteMangaRepositor
sortOrder: SortOrder?,
tag: MangaTag?
): List<Manga> {
val domain = conf.getDomain(DOMAIN)
val domain = getDomain()
val url = buildString {
append("https://")
append(domain)
@@ -51,8 +53,9 @@ class DesuMeRepository(loaderContext: MangaLoaderContext) : RemoteMangaRepositor
for (i in 0 until total) {
val jo = json.getJSONObject(i)
val cover = jo.getJSONObject("image")
val id = jo.getLong("id")
list += Manga(
url = jo.getString("url"),
url = "/manga/api/$id",
source = MangaSource.DESUME,
title = jo.getString("russian"),
altTitle = jo.getString("name"),
@@ -63,7 +66,7 @@ class DesuMeRepository(loaderContext: MangaLoaderContext) : RemoteMangaRepositor
else -> null
},
rating = jo.getDouble("score").toFloat().coerceIn(0f, 1f),
id = ID_MASK + jo.getLong("id"),
id = generateUid(id),
description = jo.getString("description")
)
}
@@ -71,10 +74,10 @@ class DesuMeRepository(loaderContext: MangaLoaderContext) : RemoteMangaRepositor
}
override suspend fun getDetails(manga: Manga): Manga {
val domain = conf.getDomain(DOMAIN)
val url = "https://$domain/manga/api/${manga.id - ID_MASK}"
val url = manga.url.withDomain()
val json = loaderContext.httpGet(url).parseJson().getJSONObject("response")
?: throw ParseException("Invalid response")
val baseChapterUrl = manga.url + "/chapter/"
return manga.copy(
tags = json.getJSONArray("genres").mapToSet {
MangaTag(
@@ -87,9 +90,9 @@ class DesuMeRepository(loaderContext: MangaLoaderContext) : RemoteMangaRepositor
chapters = json.getJSONObject("chapters").getJSONArray("list").mapIndexed { i, it ->
val chid = it.getLong("id")
MangaChapter(
id = ID_MASK + chid,
id = generateUid(chid),
source = manga.source,
url = "$url/chapter/$chid",
url = "$baseChapterUrl$chid",
name = it.optString("title", "${manga.title} #${it.getDouble("ch")}"),
number = i + 1
)
@@ -98,21 +101,22 @@ class DesuMeRepository(loaderContext: MangaLoaderContext) : RemoteMangaRepositor
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val json = loaderContext.httpGet(chapter.url).parseJson().getJSONObject("response")
?: throw ParseException("Invalid response")
return json.getJSONObject("pages").getJSONArray("list").map {
val fullUrl = chapter.url.withDomain()
val json = loaderContext.httpGet(fullUrl)
.parseJson()
.getJSONObject("response") ?: throw ParseException("Invalid response")
return json.getJSONObject("pages").getJSONArray("list").map { jo ->
MangaPage(
id = it.getLong("id"),
referer = chapter.url,
id = generateUid(jo.getLong("id")),
referer = fullUrl,
source = chapter.source,
url = it.getString("img")
url = jo.getString("img")
)
}
}
override suspend fun getTags(): Set<MangaTag> {
val domain = conf.getDomain(DOMAIN)
val doc = loaderContext.httpGet("https://$domain/manga/").parseHtml()
val doc = loaderContext.httpGet("https://${getDomain()}/manga/").parseHtml()
val root = doc.body().getElementById("animeFilter").selectFirst(".catalog-genres")
return root.select("li").mapToSet {
MangaTag(
@@ -133,10 +137,4 @@ class DesuMeRepository(loaderContext: MangaLoaderContext) : RemoteMangaRepositor
SortOrder.NEWEST -> "id"
else -> "updated"
}
private companion object {
private const val ID_MASK = 1000
private const val DOMAIN = "desu.me"
}
}

View File

@@ -13,8 +13,6 @@ import java.util.*
abstract class GroupleRepository(loaderContext: MangaLoaderContext) :
RemoteMangaRepository(loaderContext) {
protected abstract val defaultDomain: String
override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.UPDATED,
SortOrder.POPULARITY,
@@ -28,13 +26,13 @@ abstract class GroupleRepository(loaderContext: MangaLoaderContext) :
sortOrder: SortOrder?,
tag: MangaTag?
): List<Manga> {
val domain = conf.getDomain(defaultDomain)
val domain = getDomain()
val doc = when {
!query.isNullOrEmpty() -> loaderContext.httpPost(
"https://$domain/search",
mapOf(
"q" to query.urlEncoded(),
"offset" to offset.upBy(PAGE_SIZE_SEARCH).toString()
"offset" to (offset upBy PAGE_SIZE_SEARCH).toString()
)
)
tag == null -> loaderContext.httpGet(
@@ -42,14 +40,14 @@ abstract class GroupleRepository(loaderContext: MangaLoaderContext) :
getSortKey(
sortOrder
)
}&offset=${offset.upBy(PAGE_SIZE)}"
}&offset=${offset upBy PAGE_SIZE}"
)
else -> loaderContext.httpGet(
"https://$domain/list/genre/${tag.key}?sortType=${
getSortKey(
sortOrder
)
}&offset=${offset.upBy(PAGE_SIZE)}"
}&offset=${offset upBy PAGE_SIZE}"
)
}.parseHtml()
val root = doc.body().getElementById("mangaBox")
@@ -68,9 +66,10 @@ abstract class GroupleRepository(loaderContext: MangaLoaderContext) :
val title = descDiv.selectFirst("h3")?.selectFirst("a")?.text()
?: return@mapNotNull null
val tileInfo = descDiv.selectFirst("div.tile-info")
val relUrl = href.toRelativeUrl(baseHost)
Manga(
id = href.longHashCode(),
url = href,
id = generateUid(relUrl),
url = relUrl,
title = title,
altTitle = descDiv.selectFirst("h4")?.text(),
coverUrl = imgDiv.selectFirst("img.lazy")?.attr("data-original").orEmpty(),
@@ -103,8 +102,7 @@ abstract class GroupleRepository(loaderContext: MangaLoaderContext) :
}
override suspend fun getDetails(manga: Manga): Manga {
val domain = conf.getDomain(defaultDomain)
val doc = loaderContext.httpGet(manga.url).parseHtml()
val doc = loaderContext.httpGet(manga.url.withDomain()).parseHtml()
val root = doc.body().getElementById("mangaBox")?.selectFirst("div.leftContent")
?: throw ParseException("Cannot find root")
return manga.copy(
@@ -122,11 +120,10 @@ abstract class GroupleRepository(loaderContext: MangaLoaderContext) :
)
},
chapters = root.selectFirst("div.chapters-link")?.selectFirst("table")
?.select("a")?.asReversed()?.mapIndexedNotNull { i, a ->
val href =
a.attr("href")?.withDomain(domain) ?: return@mapIndexedNotNull null
?.select("a")?.asReversed()?.mapIndexed { i, a ->
val href = a.relUrl("href")
MangaChapter(
id = href.longHashCode(),
id = generateUid(href),
name = a.ownText().removePrefix(manga.title).trim(),
number = i + 1,
url = href,
@@ -137,7 +134,7 @@ abstract class GroupleRepository(loaderContext: MangaLoaderContext) :
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val doc = loaderContext.httpGet(chapter.url + "?mtr=1").parseHtml()
val doc = loaderContext.httpGet(chapter.url.withDomain() + "?mtr=1").parseHtml()
val scripts = doc.select("script")
for (script in scripts) {
val data = script.html()
@@ -153,7 +150,7 @@ abstract class GroupleRepository(loaderContext: MangaLoaderContext) :
val url = parts[0].value.removeSurrounding('"', '\'') +
parts[2].value.removeSurrounding('"', '\'')
MangaPage(
id = url.longHashCode(),
id = generateUid(url),
url = url,
referer = chapter.url,
source = source
@@ -164,8 +161,7 @@ abstract class GroupleRepository(loaderContext: MangaLoaderContext) :
}
override suspend fun getTags(): Set<MangaTag> {
val domain = conf.getDomain(defaultDomain)
val doc = loaderContext.httpGet("https://$domain/list/genres/sort_name").parseHtml()
val doc = loaderContext.httpGet("https://${getDomain()}/list/genres/sort_name").parseHtml()
val root = doc.body().getElementById("mangaBox").selectFirst("div.leftContent")
.selectFirst("table.table")
return root.select("a.element-link").mapToSet { a ->

View File

@@ -3,10 +3,8 @@ package org.koitharu.kotatsu.core.parser.site
import org.koitharu.kotatsu.base.domain.MangaLoaderContext
import org.koitharu.kotatsu.core.exceptions.ParseException
import org.koitharu.kotatsu.core.model.*
import org.koitharu.kotatsu.utils.ext.longHashCode
import org.koitharu.kotatsu.utils.ext.mapToSet
import org.koitharu.kotatsu.utils.ext.parseHtml
import org.koitharu.kotatsu.utils.ext.withDomain
class HenChanRepository(loaderContext: MangaLoaderContext) : ChanRepository(loaderContext) {
@@ -30,14 +28,13 @@ class HenChanRepository(loaderContext: MangaLoaderContext) : ChanRepository(load
}
override suspend fun getDetails(manga: Manga): Manga {
val domain = conf.getDomain(defaultDomain)
val doc = loaderContext.httpGet(manga.url).parseHtml()
val doc = loaderContext.httpGet(manga.url.withDomain()).parseHtml()
val root =
doc.body().getElementById("dle-content") ?: throw ParseException("Cannot find root")
val readLink = manga.url.replace("manga", "online")
return manga.copy(
description = root.getElementById("description")?.html()?.substringBeforeLast("<div"),
largeCoverUrl = root.getElementById("cover")?.attr("src")?.withDomain(domain),
largeCoverUrl = root.getElementById("cover")?.absUrl("src"),
tags = root.selectFirst("div.sidetags")?.select("li.sidetag")?.mapToSet {
val a = it.children().last()
MangaTag(
@@ -48,7 +45,7 @@ class HenChanRepository(loaderContext: MangaLoaderContext) : ChanRepository(load
} ?: manga.tags,
chapters = listOf(
MangaChapter(
id = readLink.longHashCode(),
id = generateUid(readLink),
url = readLink,
source = source,
number = 1,

View File

@@ -17,7 +17,7 @@ import kotlin.collections.ArrayList
open class MangaLibRepository(loaderContext: MangaLoaderContext) :
RemoteMangaRepository(loaderContext) {
protected open val defaultDomain = "mangalib.me"
override val defaultDomain = "mangalib.me"
override val source = MangaSource.MANGALIB
@@ -38,11 +38,10 @@ open class MangaLibRepository(loaderContext: MangaLoaderContext) :
if (!query.isNullOrEmpty()) {
return search(query)
}
val domain = conf.getDomain(defaultDomain)
val page = (offset / 60f).toIntUp()
val url = buildString {
append("https://")
append(domain)
append(getDomain())
append("/manga-list?dir=")
append(getSortKey(sortOrder))
append("&page=")
@@ -57,11 +56,11 @@ open class MangaLibRepository(loaderContext: MangaLoaderContext) :
val items = root.selectFirst("div.media-cards-grid").select("div.media-card-wrap")
return items.mapNotNull { card ->
val a = card.selectFirst("a.media-card") ?: return@mapNotNull null
val href = a.attr("href").withDomain(domain)
val href = a.relUrl("href")
Manga(
id = href.longHashCode(),
id = generateUid(href),
title = card.selectFirst("h3").text(),
coverUrl = a.attr("data-src").withDomain(domain),
coverUrl = a.absUrl("data-src"),
altTitle = null,
author = null,
rating = Manga.NO_RATING,
@@ -76,11 +75,12 @@ open class MangaLibRepository(loaderContext: MangaLoaderContext) :
override fun onCreatePreferences() = arraySetOf(SourceSettings.KEY_DOMAIN)
override suspend fun getDetails(manga: Manga): Manga {
val doc = loaderContext.httpGet(manga.url + "?section=info").parseHtml()
val fullUrl = manga.url.withDomain()
val doc = loaderContext.httpGet("$fullUrl?section=info").parseHtml()
val root = doc.body().getElementById("main-page") ?: throw ParseException("Root not found")
val title = root.selectFirst("div.media-header__wrap")?.children()
val info = root.selectFirst("div.media-content")
val chaptersDoc = loaderContext.httpGet(manga.url + "?section=chapters").parseHtml()
val chaptersDoc = loaderContext.httpGet("$fullUrl?section=chapters").parseHtml()
val scripts = chaptersDoc.select("script")
var chapters: ArrayList<MangaChapter>? = null
scripts@ for (script in scripts) {
@@ -109,7 +109,7 @@ open class MangaLibRepository(loaderContext: MangaLoaderContext) :
}
chapters.add(
MangaChapter(
id = url.longHashCode(),
id = generateUid(url),
url = url,
source = source,
number = total - i,
@@ -144,7 +144,8 @@ open class MangaLibRepository(loaderContext: MangaLoaderContext) :
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val doc = loaderContext.httpGet(chapter.url).parseHtml()
val fullUrl = chapter.url.withDomain()
val doc = loaderContext.httpGet(fullUrl).parseHtml()
if (doc.location()?.endsWith("/register") == true) {
throw AuthRequiredException("/login".inContextOf(doc))
}
@@ -170,9 +171,9 @@ open class MangaLibRepository(loaderContext: MangaLoaderContext) :
return pages.map { x ->
val pageUrl = "$domain/$url${x.getString("u")}"
MangaPage(
id = pageUrl.longHashCode(),
id = generateUid(pageUrl),
url = pageUrl,
referer = chapter.url,
referer = fullUrl,
source = source
)
}
@@ -182,8 +183,7 @@ open class MangaLibRepository(loaderContext: MangaLoaderContext) :
}
override suspend fun getTags(): Set<MangaTag> {
val domain = conf.getDomain(defaultDomain)
val url = "https://$domain/manga-list"
val url = "https://${getDomain()}/manga-list"
val doc = loaderContext.httpGet(url).parseHtml()
val scripts = doc.body().select("script")
for (script in scripts) {
@@ -215,14 +215,15 @@ open class MangaLibRepository(loaderContext: MangaLoaderContext) :
}
private suspend fun search(query: String): List<Manga> {
val domain = conf.getDomain(defaultDomain)
val domain = getDomain()
val json = loaderContext.httpGet("https://$domain/search?type=manga&q=$query")
.parseJsonArray()
return json.map { jo ->
val url = "https://$domain/${jo.getString("slug")}"
val slug = jo.getString("slug")
val url = "https://$domain/$slug"
val covers = jo.getJSONObject("covers")
Manga(
id = url.longHashCode(),
id = generateUid(slug),
url = url,
title = jo.getString("rus_name"),
altTitle = jo.getString("name"),

View File

@@ -15,6 +15,8 @@ class MangaTownRepository(loaderContext: MangaLoaderContext) :
override val source = MangaSource.MANGATOWN
override val defaultDomain = "www.mangatown.com"
override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.ALPHABETICAL,
SortOrder.RATING,
@@ -28,9 +30,6 @@ class MangaTownRepository(loaderContext: MangaLoaderContext) :
sortOrder: SortOrder?,
tag: MangaTag?
): List<Manga> {
val domain = conf.getDomain(DOMAIN)
val ssl = conf.isUseSsl(false)
val scheme = if (ssl) "https" else "http"
val sortKey = when (sortOrder) {
SortOrder.ALPHABETICAL -> "?name.az"
SortOrder.RATING -> "?rating.za"
@@ -43,29 +42,28 @@ class MangaTownRepository(loaderContext: MangaLoaderContext) :
if (offset != 0) {
return emptyList()
}
"$scheme://$domain/search?name=${query.urlEncoded()}"
"/search?name=${query.urlEncoded()}".withDomain()
}
tag != null -> "$scheme://$domain/directory/${tag.key}/$page.htm$sortKey"
else -> "$scheme://$domain/directory/$page.htm$sortKey"
tag != null -> "/directory/${tag.key}/$page.htm$sortKey".withDomain()
else -> "/directory/$page.htm$sortKey".withDomain()
}
val doc = loaderContext.httpGet(url).parseHtml()
val root = doc.body().selectFirst("ul.manga_pic_list")
?: throw ParseException("Root not found")
return root.select("li").mapNotNull { li ->
val a = li.selectFirst("a.manga_cover")
val href = a.attr("href").withDomain(domain, ssl)
val href = a.relUrl("href")
val views = li.select("p.view")
val status = views.findOwnText { x -> x.startsWith("Status:") }
?.substringAfter(':')?.trim()?.toLowerCase(Locale.ROOT)
Manga(
id = href.longHashCode(),
id = generateUid(href),
title = a.attr("title"),
coverUrl = a.selectFirst("img").attr("src"),
coverUrl = a.selectFirst("img").absUrl("src"),
source = MangaSource.MANGATOWN,
altTitle = null,
rating = li.selectFirst("p.score")?.selectFirst("b")
?.ownText()?.toFloatOrNull()?.div(5f) ?: Manga.NO_RATING,
largeCoverUrl = null,
author = views.findText { x -> x.startsWith("Author:") }?.substringAfter(':')
?.trim(),
state = when (status) {
@@ -86,9 +84,7 @@ class MangaTownRepository(loaderContext: MangaLoaderContext) :
}
override suspend fun getDetails(manga: Manga): Manga {
val domain = conf.getDomain(DOMAIN)
val ssl = conf.isUseSsl(false)
val doc = loaderContext.httpGet(manga.url).parseHtml()
val doc = loaderContext.httpGet(manga.url.withDomain()).parseHtml()
val root = doc.body().selectFirst("section.main")
?.selectFirst("div.article_content") ?: throw ParseException("Cannot find root")
val info = root.selectFirst("div.detail_info").selectFirst("ul")
@@ -106,11 +102,11 @@ class MangaTownRepository(loaderContext: MangaLoaderContext) :
}.orEmpty(),
description = info.getElementById("show")?.ownText(),
chapters = chaptersList?.mapIndexedNotNull { i, li ->
val href = li.selectFirst("a").attr("href").withDomain(domain, ssl)
val href = li.selectFirst("a").relUrl("href")
val name = li.select("span").filter { it.className().isEmpty() }
.joinToString(" - ") { it.text() }.trim()
MangaChapter(
id = href.longHashCode(),
id = generateUid(href),
url = href,
source = MangaSource.MANGATOWN,
number = i + 1,
@@ -121,35 +117,31 @@ class MangaTownRepository(loaderContext: MangaLoaderContext) :
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val domain = conf.getDomain(DOMAIN)
val ssl = conf.isUseSsl(false)
val doc = loaderContext.httpGet(chapter.url).parseHtml()
val fullUrl = chapter.url.withDomain()
val doc = loaderContext.httpGet(fullUrl).parseHtml()
val root = doc.body().selectFirst("div.page_select")
?: throw ParseException("Cannot find root")
return root.selectFirst("select").select("option").mapNotNull {
val href = it.attr("value").withDomain(domain, ssl)
val href = it.relUrl("value")
if (href.endsWith("featured.html")) {
return@mapNotNull null
}
MangaPage(
id = href.longHashCode(),
id = generateUid(href),
url = href,
referer = chapter.url,
referer = fullUrl,
source = MangaSource.MANGATOWN
)
}
}
override suspend fun getPageUrl(page: MangaPage): String {
val domain = conf.getDomain(DOMAIN)
val ssl = conf.isUseSsl(false)
val doc = loaderContext.httpGet(page.url).parseHtml()
return doc.getElementById("image").attr("src").withDomain(domain, ssl)
val doc = loaderContext.httpGet(page.url.withDomain()).parseHtml()
return doc.getElementById("image").absUrl("src")
}
override suspend fun getTags(): Set<MangaTag> {
val domain = conf.getDomain(DOMAIN)
val doc = loaderContext.httpGet("http://$domain/directory/").parseHtml()
val doc = loaderContext.httpGet("/directory/".withDomain()).parseHtml()
val root = doc.body().selectFirst("aside.right")
.getElementsContainingOwnText("Genres")
.first()
@@ -180,6 +172,5 @@ class MangaTownRepository(loaderContext: MangaLoaderContext) :
@Language("RegExp")
val TAG_REGEX = Regex("[^\\-]+-[^\\-]+-[^\\-]+-[^\\-]+-[^\\-]+-[^\\-]+")
const val DOMAIN = "www.mangatown.com"
}
}

View File

@@ -15,6 +15,8 @@ class MangareadRepository(
override val source = MangaSource.MANGAREAD
override val defaultDomain = "www.mangaread.org"
override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.UPDATED,
SortOrder.POPULARITY
@@ -29,7 +31,6 @@ class MangareadRepository(
if (offset % PAGE_SIZE != 0) {
return emptyList()
}
val domain = conf.getDomain(DOMAIN)
val payload = createRequestTemplate()
payload["page"] = (offset / PAGE_SIZE).toString()
payload["vars[meta_key]"] = when (sortOrder) {
@@ -40,14 +41,14 @@ class MangareadRepository(
payload["vars[wp-manga-genre]"] = tag?.key.orEmpty()
payload["vars[s]"] = query.orEmpty()
val doc = loaderContext.httpPost(
"https://${domain}/wp-admin/admin-ajax.php",
"https://${getDomain()}/wp-admin/admin-ajax.php",
payload
).parseHtml()
return doc.select("div.row.c-tabs-item__content").map { div ->
val href = div.selectFirst("a").absUrl("href")
val href = div.selectFirst("a").relUrl("href")
val summary = div.selectFirst(".tab-summary")
Manga(
id = href.longHashCode(),
id = generateUid(href),
url = href,
coverUrl = div.selectFirst("img").attr("data-srcset")
.split(',').firstOrNull()?.substringBeforeLast(' ').orEmpty(),
@@ -74,8 +75,7 @@ class MangareadRepository(
}
override suspend fun getTags(): Set<MangaTag> {
val domain = conf.getDomain(DOMAIN)
val doc = loaderContext.httpGet("https://$domain/manga/").parseHtml()
val doc = loaderContext.httpGet("https://${getDomain()}/manga/").parseHtml()
val root = doc.body().selectFirst("header")
.selectFirst("ul.second-menu")
return root.select("li").mapNotNullToSet { li ->
@@ -94,8 +94,8 @@ class MangareadRepository(
}
override suspend fun getDetails(manga: Manga): Manga {
val domain = conf.getDomain(DOMAIN)
val doc = loaderContext.httpGet(manga.url).parseHtml()
val fullUrl = manga.url.withDomain()
val doc = loaderContext.httpGet(fullUrl).parseHtml()
val root = doc.body().selectFirst("div.profile-manga")
?.selectFirst("div.summary_content")
?.selectFirst("div.post-content")
@@ -107,7 +107,7 @@ class MangareadRepository(
?.attr("data-postid")?.toLongOrNull()
?: throw ParseException("Cannot obtain manga id")
val doc2 = loaderContext.httpPost(
"https://${domain}/wp-admin/admin-ajax.php",
"https://${getDomain()}/wp-admin/admin-ajax.php",
mapOf(
"action" to "manga_get_chapters",
"manga" to mangaId.toString()
@@ -129,9 +129,9 @@ class MangareadRepository(
?.joinToString { it.html() },
chapters = doc2.select("li").asReversed().mapIndexed { i, li ->
val a = li.selectFirst("a")
val href = a.absUrl("href")
val href = a.relUrl("href")
MangaChapter(
id = href.longHashCode(),
id = generateUid(href),
name = a.ownText(),
number = i + 1,
url = href,
@@ -142,17 +142,18 @@ class MangareadRepository(
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val doc = loaderContext.httpGet(chapter.url).parseHtml()
val fullUrl = chapter.url.withDomain()
val doc = loaderContext.httpGet(fullUrl).parseHtml()
val root = doc.body().selectFirst("div.main-col-inner")
?.selectFirst("div.reading-content")
?: throw ParseException("Root not found")
return root.select("div.page-break").map { div ->
val img = div.selectFirst("img")
val url = img.absUrl("data-src")
val url = img.relUrl("data-src")
MangaPage(
id = url.longHashCode(),
id = generateUid(url),
url = url,
referer = chapter.url,
referer = fullUrl,
source = MangaSource.MANGAREAD
)
}
@@ -163,7 +164,6 @@ class MangareadRepository(
private companion object {
private const val PAGE_SIZE = 12
private const val DOMAIN = "www.mangaread.org"
private fun createRequestTemplate() =
"action=madara_load_more&page=1&template=madara-core%2Fcontent%2Fcontent-search&vars%5Bs%5D=&vars%5Borderby%5D=meta_value_num&vars%5Bpaged%5D=1&vars%5Btemplate%5D=search&vars%5Bmeta_query%5D%5B0%5D%5Brelation%5D=AND&vars%5Bmeta_query%5D%5Brelation%5D=OR&vars%5Bpost_type%5D=wp-manga&vars%5Bpost_status%5D=publish&vars%5Bmeta_key%5D=_latest_update&vars%5Border%5D=desc&vars%5Bmanga_archives_item_layout%5D=default"

View File

@@ -1,7 +1,6 @@
package org.koitharu.kotatsu.core.parser.site
import androidx.collection.arraySetOf
import okhttp3.HttpUrl.Companion.toHttpUrl
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
@@ -18,6 +17,8 @@ class RemangaRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposito
override val source = MangaSource.REMANGA
override val defaultDomain = "remanga.org"
override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.POPULARITY,
SortOrder.RATING,
@@ -32,7 +33,7 @@ class RemangaRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposito
sortOrder: SortOrder?,
tag: MangaTag?
): List<Manga> {
val domain = conf.getDomain(DEFAULT_DOMAIN)
val domain = getDomain()
val urlBuilder = StringBuilder()
.append("https://api.")
.append(domain)
@@ -40,20 +41,21 @@ class RemangaRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposito
urlBuilder.append("/api/search/?query=")
.append(query.urlEncoded())
} else {
urlBuilder.append("/api/search/catalog/?page=")
.append("&ordering=")
urlBuilder.append("/api/search/catalog/?ordering=")
.append(getSortKey(sortOrder))
if (tag != null) {
urlBuilder.append("&genres=" + tag.key)
}
}
urlBuilder.append((offset / PAGE_SIZE) + 1)
urlBuilder
.append("&page=")
.append((offset / PAGE_SIZE) + 1)
.append("&count=")
.append(PAGE_SIZE)
val content = loaderContext.httpGet(urlBuilder.toString()).parseJson()
.getJSONArray("content")
return content.map { jo ->
val url = "https://$domain/manga/${jo.getString("dir")}"
val url = "/manga/${jo.getString("dir")}"
val img = jo.getJSONObject("img")
Manga(
id = generateUid(url),
@@ -77,8 +79,9 @@ class RemangaRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposito
}
override suspend fun getDetails(manga: Manga): Manga {
val domain = conf.getDomain(DEFAULT_DOMAIN)
val slug = manga.url.toHttpUrl().pathSegments.last()
val domain = getDomain()
val slug = manga.url.find(LAST_URL_PATH_REGEX)
?: throw ParseException("Cannot obtain slug from ${manga.url}")
val data = loaderContext.httpGet(
url = "https://api.$domain/api/titles/$slug/"
).parseJson()
@@ -110,7 +113,7 @@ class RemangaRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposito
val id = jo.getLong("id")
val name = jo.getString("name")
MangaChapter(
id = generateUid(id.toInt()),
id = generateUid(id),
url = "https://api.$domain/api/titles/chapters/$id/",
number = chapters.length() - i,
name = buildString {
@@ -128,8 +131,8 @@ class RemangaRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposito
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val referer = "https://${conf.getDomain(DEFAULT_DOMAIN)}/"
val content = loaderContext.httpGet(chapter.url).parseJson()
val referer = "https://${getDomain()}/"
val content = loaderContext.httpGet(chapter.url.withDomain()).parseJson()
.getJSONObject("content").getJSONArray("pages")
val pages = ArrayList<MangaPage>(content.length())
for (i in 0 until content.length()) {
@@ -143,7 +146,7 @@ class RemangaRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposito
}
override suspend fun getTags(): Set<MangaTag> {
val domain = conf.getDomain(DEFAULT_DOMAIN)
val domain = getDomain()
val content = loaderContext.httpGet("https://api.$domain/api/forms/titles/?get=genres")
.parseJson().getJSONObject("content").getJSONArray("genres")
return content.mapToSet { jo ->
@@ -166,7 +169,7 @@ class RemangaRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposito
}
private fun parsePage(jo: JSONObject, referer: String) = MangaPage(
id = generateUid(jo.getLong("id").toInt()),
id = generateUid(jo.getLong("id")),
url = jo.getString("link"),
referer = referer,
source = source
@@ -175,9 +178,10 @@ class RemangaRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposito
private companion object {
const val PAGE_SIZE = 30
const val DEFAULT_DOMAIN = "remanga.org"
const val STATUS_ONGOING = 1
const val STATUS_FINISHED = 0
val LAST_URL_PATH_REGEX = Regex("/[^/]+/?$")
}
}

View File

@@ -5,9 +5,8 @@ import org.koitharu.kotatsu.core.exceptions.ParseException
import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaChapter
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.utils.ext.longHashCode
import org.koitharu.kotatsu.utils.ext.parseHtml
import org.koitharu.kotatsu.utils.ext.withDomain
import org.koitharu.kotatsu.utils.ext.relUrl
class YaoiChanRepository(loaderContext: MangaLoaderContext) : ChanRepository(loaderContext) {
@@ -15,20 +14,18 @@ class YaoiChanRepository(loaderContext: MangaLoaderContext) : ChanRepository(loa
override val defaultDomain = "yaoi-chan.me"
override suspend fun getDetails(manga: Manga): Manga {
val domain = conf.getDomain(defaultDomain)
val doc = loaderContext.httpGet(manga.url).parseHtml()
val doc = loaderContext.httpGet(manga.url.withDomain()).parseHtml()
val root =
doc.body().getElementById("dle-content") ?: throw ParseException("Cannot find root")
return manga.copy(
description = root.getElementById("description")?.html()?.substringBeforeLast("<div"),
largeCoverUrl = root.getElementById("cover")?.attr("src")?.withDomain(domain),
largeCoverUrl = root.getElementById("cover")?.absUrl("src"),
chapters = root.select("table.table_cha").flatMap { table ->
table.select("div.manga")
}.mapNotNull { it.selectFirst("a") }.reversed().mapIndexedNotNull { i, a ->
val href = a.attr("href")
?.withDomain(domain) ?: return@mapIndexedNotNull null
}.mapNotNull { it.selectFirst("a") }.reversed().mapIndexed { i, a ->
val href = a.relUrl("href")
MangaChapter(
id = href.longHashCode(),
id = generateUid(href),
name = a.text().trim(),
number = i + 1,
url = href,

View File

@@ -217,7 +217,7 @@ class ReaderViewModel(
private suspend fun loadChapter(chapterId: Long): List<ReaderPage> {
val manga = checkNotNull(mangaData.value) { "Manga is null" }
val chapter = checkNotNull(chapters.get(chapterId)) { "Chapter $chapterId not found" }
val chapter = checkNotNull(chapters[chapterId]) { "Requested chapter not found" }
val repo = manga.source.repository
return repo.getPages(chapter).mapIndexed { index, page ->
ReaderPage.from(page, index, chapterId)

View File

@@ -7,6 +7,7 @@ import org.json.JSONObject
import org.jsoup.Jsoup
import org.jsoup.internal.StringUtil
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.jsoup.nodes.Node
import org.jsoup.select.Elements
@@ -69,4 +70,25 @@ fun String.inContextOf(node: Node): String {
} else {
StringUtil.resolve(node.baseUri(), this)
}
}
}
fun String.toRelativeUrl(domain: String): String {
if (isEmpty() || startsWith("/")) {
return this
}
return replace(Regex("^[^/]{2,6}://${Regex.escape(domain)}+/", RegexOption.IGNORE_CASE), "/")
}
fun Element.relUrl(attributeKey: String): String {
val attr = attr(attributeKey)
if (attr.isEmpty()) {
return ""
}
if (attr.startsWith("/")) {
return attr
}
val baseUrl = REGEX_URL_BASE.find(baseUri())?.value ?: return attr
return attr.removePrefix(baseUrl.dropLast(1))
}
private val REGEX_URL_BASE = Regex("^[^/]{2,6}://[^/]+/", RegexOption.IGNORE_CASE)

View File

@@ -33,7 +33,7 @@ fun Float.toIntUp(): Int {
}
}
fun Int.upBy(step: Int): Int {
infix fun Int.upBy(step: Int): Int {
val mod = this % step
return if (mod == this) {
this

View File

@@ -16,28 +16,6 @@ fun String.longHashCode(): Long {
return h
}
@Deprecated("Use String.inContextOf")
fun String.withDomain(domain: String, ssl: Boolean = true) = when {
this.startsWith("//") -> buildString {
append("http")
if (ssl) {
append('s')
}
append(":")
append(this@withDomain)
}
this.startsWith("/") -> buildString {
append("http")
if (ssl) {
append('s')
}
append("://")
append(domain)
append(this@withDomain)
}
else -> this
}
fun String.removeSurrounding(vararg chars: Char): String {
if (length == 0) {
return this
@@ -124,4 +102,6 @@ fun String.substringBetween(from: String, to: String, fallbackValue: String): St
} else {
substring(fromIndex + from.length, toIndex)
}
}
}
fun String.find(regex: Regex) = regex.find(this)?.value