Mangachan parsers

This commit is contained in:
Koitharu
2020-03-01 20:48:59 +02:00
parent 4dff9f5bf1
commit 56b529331e
12 changed files with 183 additions and 11 deletions

View File

@@ -4,9 +4,7 @@ import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
import org.koitharu.kotatsu.core.parser.LocalMangaRepository
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.parser.site.MintMangaRepository
import org.koitharu.kotatsu.core.parser.site.ReadmangaRepository
import org.koitharu.kotatsu.core.parser.site.SelfMangaRepository
import org.koitharu.kotatsu.core.parser.site.*
@Suppress("SpellCheckingInspection")
@Parcelize
@@ -18,5 +16,8 @@ enum class MangaSource(
LOCAL("Local", null, LocalMangaRepository::class.java),
READMANGA_RU("ReadManga", "ru", ReadmangaRepository::class.java),
MINTMANGA("MintManga", "ru", MintMangaRepository::class.java),
SELFMANGA("SelfManga", "ru", SelfMangaRepository::class.java)
SELFMANGA("SelfManga", "ru", SelfMangaRepository::class.java),
MANGACHAN("Манга-тян", "ru", MangaChanRepository::class.java),
HENCHAN("Хентай-тян", "ru", HenChanRepository::class.java),
YAOICHAN("Яой-тян", "ru", YaoiChanRepository::class.java)
}

View File

@@ -0,0 +1,137 @@
package org.koitharu.kotatsu.core.parser.site
import org.koitharu.kotatsu.core.exceptions.ParseException
import org.koitharu.kotatsu.core.model.*
import org.koitharu.kotatsu.core.parser.BaseMangaRepository
import org.koitharu.kotatsu.domain.MangaLoaderContext
import org.koitharu.kotatsu.utils.ext.*
abstract class ChanRepository(
private val source: MangaSource,
loaderContext: MangaLoaderContext
) : BaseMangaRepository(loaderContext) {
protected abstract val domain: String
override val sortOrders = setOf(SortOrder.NEWEST, SortOrder.POPULARITY, SortOrder.ALPHABETICAL)
override suspend fun getList(
offset: Int,
query: String?,
sortOrder: SortOrder?,
tag: MangaTag?
): List<Manga> {
val url = when {
query != null -> "https://$domain/?do=search&subaction=search&story=${query.urlEncoded()}"
tag != null -> "https://$domain/tags/${tag.key}&n=${getSortKey2(sortOrder)}?offset=$offset"
else -> "https://$domain/${getSortKey(sortOrder)}?offset=$offset"
}
val doc = loaderContext.get(url).parseHtml()
val root = doc.body().selectFirst("div.main_fon").getElementById("content")
?: throw ParseException("Cannot find root")
return root.select("div.content_row").mapNotNull { row ->
val a = row.selectFirst("div.manga_row1")?.selectFirst("a.title_link")
?: return@mapNotNull null
val href = a.attr("href").withDomain(domain)
Manga(
id = href.longHashCode(),
url = href,
altTitle = a.attr("title"),
title = a.text().substringAfterLast('(').substringBeforeLast(')'),
author = row.getElementsByAttributeValueStarting(
"href",
"/mangaka"
).firstOrNull()?.text(),
coverUrl = row.selectFirst("div.manga_images")?.selectFirst("img")
?.attr("src")?.withDomain(domain).orEmpty(),
tags = safe {
row.selectFirst("div.genre")?.select("a")?.map {
MangaTag(
title = it.text(),
key = it.attr("href").substringAfterLast('/').urlEncoded(),
source = source
)
}?.toSet()
}.orEmpty(),
source = source
)
}
}
override suspend fun getDetails(manga: Manga): Manga {
val doc = loaderContext.get(manga.url).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),
chapters = root.select("table.table_cha").flatMap { table ->
table.select("div.manga2")
}.mapNotNull { it.selectFirst("a") }.mapIndexedNotNull { i, a ->
val href = a.attr("href")
?.withDomain(domain) ?: return@mapIndexedNotNull null
MangaChapter(
id = href.longHashCode(),
name = a.text().trim(),
number = i + 1,
url = href,
source = source
)
}
)
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val doc = loaderContext.get(chapter.url).parseHtml()
val scripts = doc.select("script")
for (script in scripts) {
val data = script.html()
val pos = data.indexOf("\"fullimg")
if (pos == -1) {
continue
}
val json = data.substring(pos).substringAfter('[').substringBefore(';')
.substringBeforeLast(']')
return json.split(",").map {
val url = it.trim().removeSurrounding('"')
MangaPage(
id = url.longHashCode(),
url = url,
source = source
)
}
}
throw ParseException("Pages list not found at ${chapter.url}")
}
override suspend fun getTags(): Set<MangaTag> {
val doc = loaderContext.get("https://$domain/catalog").parseHtml()
val root = doc.body().selectFirst("div.main_fon").getElementById("side")
.select("ul").last()
return root.select("li.sidetag").map { li ->
val a = li.children().last()
MangaTag(
title = a.text().capitalize(),
key = a.attr("href").substringAfterLast('/'),
source = source
)
}.toSet()
}
private fun getSortKey(sortOrder: SortOrder?) =
when (sortOrder ?: sortOrders.minBy { it.ordinal }) {
SortOrder.ALPHABETICAL -> "catalog"
SortOrder.POPULARITY -> "mostfavorites"
SortOrder.NEWEST -> "manga/new"
else -> "mostfavorites"
}
private fun getSortKey2(sortOrder: SortOrder?) =
when (sortOrder ?: sortOrders.minBy { it.ordinal }) {
SortOrder.ALPHABETICAL -> "abcasc"
SortOrder.POPULARITY -> "favdesc"
SortOrder.NEWEST -> "datedesc"
else -> "favdesc"
}
}

View File

@@ -87,7 +87,7 @@ abstract class GroupleRepository(
override suspend fun getDetails(manga: Manga): Manga {
val doc = loaderContext.get(manga.url).parseHtml()
val root = doc.body().getElementById("mangaBox")
val root = doc.body().getElementById("mangaBox") ?: throw ParseException("Cannot find root")
return manga.copy(
description = root.selectFirst("div.manga-description").firstChild()?.html(),
largeCoverUrl = root.selectFirst("div.subject-cower")?.selectFirst("img")?.attr(

View File

@@ -0,0 +1,10 @@
package org.koitharu.kotatsu.core.parser.site
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.domain.MangaLoaderContext
class HenChanRepository(loaderContext: MangaLoaderContext) :
ChanRepository(MangaSource.HENCHAN, loaderContext) {
override val domain: String = "h-chan.me"
}

View File

@@ -0,0 +1,10 @@
package org.koitharu.kotatsu.core.parser.site
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.domain.MangaLoaderContext
class MangaChanRepository(loaderContext: MangaLoaderContext) :
ChanRepository(MangaSource.MANGACHAN, loaderContext) {
override val domain: String = "manga-chan.me"
}

View File

@@ -0,0 +1,10 @@
package org.koitharu.kotatsu.core.parser.site
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.domain.MangaLoaderContext
class YaoiChanRepository(loaderContext: MangaLoaderContext) :
ChanRepository(MangaSource.YAOICHAN, loaderContext) {
override val domain: String = "yaoi-chan.me"
}

View File

@@ -49,7 +49,7 @@ class ChaptersFragment : BaseFragment(R.layout.fragment_chapters), MangaDetailsV
progressBar.isVisible = isLoading
}
override fun onError(e: Exception) = Unit //handled in activity
override fun onError(e: Throwable) = Unit //handled in activity
override fun onMangaRemoved(manga: Manga) = Unit //handled in activity

View File

@@ -68,7 +68,7 @@ class MangaDetailsActivity : BaseActivity(), MangaDetailsView {
finish()
}
override fun onError(e: Exception) {
override fun onError(e: Throwable) {
Snackbar.make(pager, e.getDisplayMessage(resources), Snackbar.LENGTH_LONG).show()
}

View File

@@ -82,7 +82,7 @@ class MangaDetailsFragment : BaseFragment(R.layout.fragment_details), MangaDetai
progressBar.isVisible = isLoading
}
override fun onError(e: Exception) = Unit //handled in activity
override fun onError(e: Throwable) = Unit //handled in activity
override fun onMangaRemoved(manga: Manga) = Unit //handled in activity

View File

@@ -52,7 +52,7 @@ class MangaDetailsPresenter private constructor() : BasePresenter<MangaDetailsVi
}
viewState.onMangaUpdated(data)
this@MangaDetailsPresenter.manga = data
} catch (e: Exception) {
} catch (e: Throwable) {
if (BuildConfig.DEBUG) {
e.printStackTrace()
}

View File

@@ -17,7 +17,7 @@ interface MangaDetailsView : MvpView {
fun onLoadingStateChanged(isLoading: Boolean)
@OneExecution
fun onError(e: Exception)
fun onError(e: Throwable)
@AddToEndSingle
fun onHistoryChanged(history: MangaHistory?)

View File

@@ -1,5 +1,7 @@
package org.koitharu.kotatsu.utils.ext
import java.net.URLEncoder
fun String.longHashCode(): Long {
var h = 1125899906842597L
val len: Int = this.length
@@ -61,4 +63,6 @@ fun String.toFileName() = this.transliterate(false)
fun String.ellipsize(maxLength: Int) = if (this.length > maxLength) {
this.take(maxLength - 1) + Typography.ellipsis
} else this
} else this
fun String.urlEncoded(): String = URLEncoder.encode(this, Charsets.UTF_8.name())