Mangachan parsers
This commit is contained in:
@@ -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)
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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?)
|
||||
|
||||
@@ -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())
|
||||
Reference in New Issue
Block a user