Add mangalib source

This commit is contained in:
Koitharu
2020-05-10 18:52:00 +03:00
parent c3c43dce3d
commit b438898456
6 changed files with 232 additions and 8 deletions

View File

@@ -21,5 +21,7 @@ enum class MangaSource(
DESUME("Desu.me", "ru", DesuMeRepository::class.java),
HENCHAN("Хентай-тян", "ru", HenChanRepository::class.java),
YAOICHAN("Яой-тян", "ru", YaoiChanRepository::class.java),
MANGATOWN("MangaTown", "en", MangaTownRepository::class.java)
MANGATOWN("MangaTown", "en", MangaTownRepository::class.java),
MANGALIB("MangaLib", "ru", MangaLibRepository::class.java),
HENTAILIB("HentaiLib", "ru", HentaiLibRepository::class.java)
}

View File

@@ -0,0 +1,11 @@
package org.koitharu.kotatsu.core.parser.site
import org.koitharu.kotatsu.core.model.MangaSource
class HentaiLibRepository : MangaLibRepository() {
protected override val defaultDomain = "hentailib.me"
override val source = MangaSource.HENTAILIB
}

View File

@@ -0,0 +1,196 @@
package org.koitharu.kotatsu.core.parser.site
import org.json.JSONArray
import org.json.JSONObject
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.ParseException
import org.koitharu.kotatsu.core.model.*
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.utils.ext.*
open class MangaLibRepository : RemoteMangaRepository() {
protected open val defaultDomain = "mangalib.me"
override val source = MangaSource.MANGALIB
override val sortOrders = setOf(
SortOrder.RATING,
SortOrder.ALPHABETICAL,
SortOrder.POPULARITY,
SortOrder.UPDATED,
SortOrder.NEWEST
)
override suspend fun getList(
offset: Int,
query: String?,
sortOrder: SortOrder?,
tag: MangaTag?
): List<Manga> {
val domain = conf.getDomain(defaultDomain)
val page = (offset / 60f).toIntUp()
val url = buildString {
append("https://")
append(domain)
append("/manga-list?dir=")
append(getSortKey(sortOrder))
append("&page=")
append(page)
if (tag != null) {
append("&includeGenres[]=")
append(tag.key)
}
}
val doc = loaderContext.httpGet(url).parseHtml()
val root = doc.body().getElementById("manga-list") ?: throw ParseException("Root not found")
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)
Manga(
id = href.longHashCode(),
title = card.selectFirst("h3").text(),
coverUrl = a.attr("data-src").withDomain(domain),
altTitle = null,
author = null,
rating = Manga.NO_RATING,
url = href,
tags = emptySet(),
state = null,
source = source
)
}
}
override fun onCreatePreferences() = setOf(R.string.key_parser_domain)
override suspend fun getDetails(manga: Manga): Manga {
val doc = loaderContext.httpGet(manga.url + "?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 scripts = chaptersDoc.body().select("script")
var chapters: ArrayList<MangaChapter>? = null
scripts@ for (script in scripts) {
val raw = script.html().lines()
for (line in raw) {
if (line.startsWith("window.__CHAPTERS_DATA__")) {
val json = JSONObject(line.substringAfter('=').substringBeforeLast(';'))
val list = json.getJSONArray("list")
val total = list.length()
chapters = ArrayList(total)
for (i in 0 until total) {
val item = list.getJSONObject(i)
val url = buildString {
append(manga.url)
append("/v")
append(item.getInt("chapter_volume"))
append("/c")
append(item.getString("chapter_number"))
append('/')
append(item.getJSONArray("teams").getJSONObject(0).getString("slug"))
}
var name = item.getString("chapter_name")
if (name.isNullOrBlank() || name == "null") {
name = "Том " + item.getInt("chapter_volume") +
" Глава " + item.getString("chapter_number")
}
chapters.add(
MangaChapter(
id = url.longHashCode(),
url = url,
source = source,
number = total - i,
name = name
)
)
}
break@scripts
}
}
}
return manga.copy(
title = title?.getOrNull(0)?.text()?.takeUnless(String::isBlank) ?: manga.title,
altTitle = title?.getOrNull(1)?.text()?.substringBefore('/')?.trim(),
rating = root.selectFirst("div.media-stats-item__score")
?.selectFirst("span")
?.text()?.toFloatOrNull()?.div(5f) ?: manga.rating,
author = info.getElementsMatchingOwnText("Автор").firstOrNull()
?.nextElementSibling()?.text() ?: manga.author,
tags = info.getElementsMatchingOwnText("Жанры")?.firstOrNull()
?.nextElementSibling()?.select("a")?.mapNotNull { a ->
MangaTag(
title = a.text(),
key = a.attr("href").substringAfterLast('='),
source = source
)
}?.toSet() ?: manga.tags,
description = info.getElementsMatchingOwnText("Описание")?.firstOrNull()
?.nextElementSibling()?.html(),
chapters = chapters
)
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val doc = loaderContext.httpGet(chapter.url).parseHtml()
val scripts = doc.head().select("script")
val pg = doc.body().getElementById("pg").html().substringAfter('=').substringBeforeLast(';')
val pages = JSONArray(pg)
for (script in scripts) {
val raw = script.html().trim()
if (raw.startsWith("window.__info")) {
val json = JSONObject(raw.substringAfter('=').substringBeforeLast(';'))
val domain = json.getJSONObject("servers").run {
getStringOrNull("main") ?: getString(
json.getJSONObject("img").getString("server")
)
}
val url = json.getJSONObject("img").getString("url")
return pages.map { x ->
val pageUrl = "$domain$url${x.getString("u")}"
MangaPage(
id = pageUrl.longHashCode(),
source = source,
url = pageUrl
)
}
}
}
throw ParseException("Script with info not found")
}
override suspend fun getTags(): Set<MangaTag> {
val domain = conf.getDomain(defaultDomain)
val url = "https://$domain/manga-list"
val doc = loaderContext.httpGet(url).parseHtml()
val scripts = doc.body().select("script")
for (script in scripts) {
val raw = script.html().trim()
if (raw.startsWith("window.__DATA")) {
val json = JSONObject(raw.substringAfter('=').substringBeforeLast(';'))
val genres = json.getJSONObject("filters").getJSONArray("genres")
val result = HashSet<MangaTag>(genres.length())
for (x in genres) {
result += MangaTag(
source = source,
key = x.getInt("id").toString(),
title = x.getString("name")
)
}
return result
}
}
throw ParseException("Script with genres not found")
}
private fun getSortKey(sortOrder: SortOrder?) = when (sortOrder) {
SortOrder.RATING -> "desc&sort=rate"
SortOrder.ALPHABETICAL -> "asc&sort=name"
SortOrder.POPULARITY -> "desc&sort=views"
SortOrder.UPDATED -> "desc&sort=last_chapter_at"
SortOrder.NEWEST -> "desc&sort=created_at"
else -> "desc&sort=last_chapter_at"
}
}

View File

@@ -6,7 +6,7 @@ import org.json.JSONObject
fun <T> JSONArray.map(block: (JSONObject) -> T): List<T> {
val len = length()
val result = ArrayList<T>(len)
for(i in 0 until len) {
for (i in 0 until len) {
val jo = getJSONObject(i)
result.add(block(jo))
}
@@ -16,11 +16,24 @@ fun <T> JSONArray.map(block: (JSONObject) -> T): List<T> {
fun <T> JSONArray.mapIndexed(block: (Int, JSONObject) -> T): List<T> {
val len = length()
val result = ArrayList<T>(len)
for(i in 0 until len) {
for (i in 0 until len) {
val jo = getJSONObject(i)
result.add(block(i, jo))
}
return result
}
fun JSONObject.getStringOrNull(name: String): String? = opt(name)?.toString()
fun JSONObject.getStringOrNull(name: String): String? = opt(name)?.toString()
operator fun JSONArray.iterator(): Iterator<JSONObject> = JSONIterator(this)
private class JSONIterator(private val array: JSONArray) : Iterator<JSONObject> {
private val total = array.length()
private var index = 0
override fun hasNext() = index < total - 1
override fun next(): JSONObject = array.getJSONObject(index++)
}

View File

@@ -22,7 +22,7 @@ class RemoteRepositoryTest(source: MangaSource) {
private val repo = MangaProviderFactory.create(source)
@Test
fun getList() {
fun list() {
val list = runBlocking { repo.getList(60) }
Assert.assertFalse(list.isEmpty())
val item = list.random()
@@ -42,7 +42,7 @@ class RemoteRepositoryTest(source: MangaSource) {
}
@Test
fun getTags() {
fun tags() {
val tags = runBlocking { repo.getTags() }
Assert.assertFalse(tags.isEmpty())
val tag = tags.random()
@@ -57,7 +57,7 @@ class RemoteRepositoryTest(source: MangaSource) {
}
@Test
fun getDetails() {
fun details() {
val manga = runBlocking { repo.getList(0) }.random()
val details = runBlocking { repo.getDetails(manga) }
Assert.assertFalse(details.chapters.isNullOrEmpty())
@@ -68,7 +68,7 @@ class RemoteRepositoryTest(source: MangaSource) {
}
@Test
fun getPages() {
fun pages() {
val manga = runBlocking { repo.getList(0) }.random()
val details = runBlocking { repo.getDetails(manga) }
val pages = runBlocking { repo.getPages(details.chapters!!.random()) }

View File

@@ -5,4 +5,6 @@ import org.koitharu.kotatsu.core.prefs.SourceConfig
class SourceConfigMock : SourceConfig {
override fun getDomain(defaultValue: String) = defaultValue
override fun isUseSsl(defaultValue: Boolean) = defaultValue
}