MangaInUa: fix chapter list and page list (#17540)
|
@ -5,7 +5,7 @@ ext {
|
||||||
extName = 'MangaInUa'
|
extName = 'MangaInUa'
|
||||||
pkgNameSuffix = 'uk.mangainua'
|
pkgNameSuffix = 'uk.mangainua'
|
||||||
extClass = '.Mangainua'
|
extClass = '.Mangainua'
|
||||||
extVersionCode = 3
|
extVersionCode = 4
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 9.7 KiB |
|
@ -7,13 +7,17 @@ import eu.kanade.tachiyomi.source.model.Page
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||||
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
import okhttp3.FormBody
|
import okhttp3.FormBody
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
|
import org.jsoup.Jsoup
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
|
import org.jsoup.select.Elements
|
||||||
|
import org.jsoup.select.Evaluator
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
|
@ -27,7 +31,7 @@ class Mangainua : ParsedHttpSource() {
|
||||||
|
|
||||||
override val client: OkHttpClient = network.cloudflareClient
|
override val client: OkHttpClient = network.cloudflareClient
|
||||||
|
|
||||||
override fun headersBuilder(): Headers.Builder = Headers.Builder()
|
override fun headersBuilder(): Headers.Builder = super.headersBuilder()
|
||||||
.add("Referer", baseUrl)
|
.add("Referer", baseUrl)
|
||||||
|
|
||||||
// Popular
|
// Popular
|
||||||
|
@ -37,11 +41,11 @@ class Mangainua : ParsedHttpSource() {
|
||||||
override fun popularMangaSelector() = "div.owl-carousel div.card--big"
|
override fun popularMangaSelector() = "div.owl-carousel div.card--big"
|
||||||
override fun popularMangaFromElement(element: Element): SManga {
|
override fun popularMangaFromElement(element: Element): SManga {
|
||||||
return SManga.create().apply {
|
return SManga.create().apply {
|
||||||
element.select("h3.card__title a").first()!!.let {
|
element.selectFirst("h3.card__title a")!!.let {
|
||||||
setUrlWithoutDomain(it.attr("href"))
|
setUrlWithoutDomain(it.attr("href"))
|
||||||
title = it.text()
|
title = it.text()
|
||||||
}
|
}
|
||||||
thumbnail_url = element.select("img").attr("abs:src")
|
thumbnail_url = element.selectFirst("img")?.absUrl("src")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
override fun popularMangaNextPageSelector() = "not used"
|
override fun popularMangaNextPageSelector() = "not used"
|
||||||
|
@ -53,11 +57,11 @@ class Mangainua : ParsedHttpSource() {
|
||||||
override fun latestUpdatesSelector() = "main.main article.item"
|
override fun latestUpdatesSelector() = "main.main article.item"
|
||||||
override fun latestUpdatesFromElement(element: Element): SManga {
|
override fun latestUpdatesFromElement(element: Element): SManga {
|
||||||
return SManga.create().apply {
|
return SManga.create().apply {
|
||||||
element.select("h3.card__title a").first()!!.let {
|
element.selectFirst("h3.card__title a")!!.let {
|
||||||
setUrlWithoutDomain(it.attr("href"))
|
setUrlWithoutDomain(it.attr("href"))
|
||||||
title = it.text()
|
title = it.text()
|
||||||
}
|
}
|
||||||
thumbnail_url = element.select("div.card--big img").attr("abs:data-src")
|
thumbnail_url = element.selectFirst("div.card--big img")?.absUrl("data-src")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
override fun latestUpdatesNextPageSelector() = "a:contains(Наступна)"
|
override fun latestUpdatesNextPageSelector() = "a:contains(Наступна)"
|
||||||
|
@ -76,18 +80,18 @@ class Mangainua : ParsedHttpSource() {
|
||||||
headers = headers,
|
headers = headers,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
throw UnsupportedOperationException("Запит має містити щонайменше 3 символи / The query must contain at least 3 characters")
|
throw Exception("Запит має містити щонайменше 3 символи / The query must contain at least 3 characters")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun searchMangaSelector() = latestUpdatesSelector()
|
override fun searchMangaSelector() = latestUpdatesSelector()
|
||||||
override fun searchMangaFromElement(element: Element): SManga {
|
override fun searchMangaFromElement(element: Element): SManga {
|
||||||
return SManga.create().apply {
|
return SManga.create().apply {
|
||||||
element.select("h3.card__title a").first()!!.let {
|
element.selectFirst("h3.card__title a")!!.let {
|
||||||
setUrlWithoutDomain(it.attr("href"))
|
setUrlWithoutDomain(it.attr("href"))
|
||||||
title = it.text()
|
title = it.text()
|
||||||
}
|
}
|
||||||
thumbnail_url = element.select("div.card--big img").attr("abs:src")
|
thumbnail_url = element.selectFirst("div.card--big img")?.absUrl("src")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
override fun searchMangaNextPageSelector() = latestUpdatesNextPageSelector()
|
override fun searchMangaNextPageSelector() = latestUpdatesNextPageSelector()
|
||||||
|
@ -95,35 +99,40 @@ class Mangainua : ParsedHttpSource() {
|
||||||
// Manga Details
|
// Manga Details
|
||||||
override fun mangaDetailsParse(document: Document): SManga {
|
override fun mangaDetailsParse(document: Document): SManga {
|
||||||
return SManga.create().apply {
|
return SManga.create().apply {
|
||||||
title = document.select("span.UAname").text()
|
title = document.selectFirst("span.UAname")!!.text()
|
||||||
description = document.select("div.item__full-description").text()
|
description = document.selectFirst("div.item__full-description")!!.text()
|
||||||
thumbnail_url = document.select("div.item__full-sidebar--poster img").first()!!.attr("abs:src")
|
thumbnail_url = document.selectFirst("div.item__full-sidebar--poster img")!!.absUrl("src")
|
||||||
status = when (document.select("div.item__full-sideba--header:has(div:containsOwn(Статус перекладу:))").first()?.select("span.item__full-sidebar--description")?.first()?.text()) {
|
status = when (document.selectFirst("div.item__full-sideba--header:has(div:containsOwn(Статус перекладу:))")?.selectFirst("span.item__full-sidebar--description")?.text()) {
|
||||||
"Триває" -> SManga.ONGOING
|
"Триває" -> SManga.ONGOING
|
||||||
"Покинуто" -> SManga.CANCELLED
|
"Покинуто" -> SManga.CANCELLED
|
||||||
"Закінчений" -> SManga.COMPLETED
|
"Закінчений" -> SManga.COMPLETED
|
||||||
else -> SManga.UNKNOWN
|
else -> SManga.UNKNOWN
|
||||||
}
|
}
|
||||||
val type = when (document.select("div.item__full-sideba--header:has(div:containsOwn(Тип:))").first()?.select("span.item__full-sidebar--description")?.first()!!.text()) {
|
val type = when (document.selectFirst("div.item__full-sideba--header:has(div:containsOwn(Тип:))")?.selectFirst("span.item__full-sidebar--description")!!.text()) {
|
||||||
"ВЕБМАНХВА" -> "Manhwa"
|
"ВЕБМАНХВА" -> "Manhwa"
|
||||||
"МАНХВА" -> "Manhwa"
|
"МАНХВА" -> "Manhwa"
|
||||||
"МАНЬХВА" -> "Manhua"
|
"МАНЬХВА" -> "Manhua"
|
||||||
"ВЕБМАНЬХВА" -> "Manhua"
|
"ВЕБМАНЬХВА" -> "Manhua"
|
||||||
else -> "Manga"
|
else -> "Manga"
|
||||||
}
|
}
|
||||||
genre = document.select("div.item__full-sideba--header:has(div:containsOwn(Жанри:))").first()?.select("span.item__full-sidebar--description")?.first()!!.select("a").joinToString { it.text() } + ", " + type
|
genre = document.selectFirst("div.item__full-sideba--header:has(div:containsOwn(Жанри:))")?.selectFirst("span.item__full-sidebar--description")!!.select("a").joinToString { it.text() } + ", " + type
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chapters
|
// Chapters
|
||||||
override fun chapterListSelector() = "div.ltcitems"
|
override fun chapterListSelector() = throw UnsupportedOperationException()
|
||||||
|
|
||||||
private var previousChapterName: String? = null
|
|
||||||
private var previousChapterNumber: Float = 0.0f
|
|
||||||
|
|
||||||
override fun chapterFromElement(element: Element): SChapter {
|
override fun chapterFromElement(element: Element): SChapter {
|
||||||
return SChapter.create().apply {
|
throw UnsupportedOperationException()
|
||||||
element.select("a").let { urlElement ->
|
}
|
||||||
|
|
||||||
|
private fun parseChapterElements(elements: Elements): List<SChapter> {
|
||||||
|
var previousChapterName: String? = null
|
||||||
|
var previousChapterNumber: Float = 0.0f
|
||||||
|
val dateFormat = DATE_FORMATTER
|
||||||
|
return elements.map { element ->
|
||||||
|
SChapter.create().apply {
|
||||||
|
val urlElement = element.selectFirst("a")!!
|
||||||
setUrlWithoutDomain(urlElement.attr("href"))
|
setUrlWithoutDomain(urlElement.attr("href"))
|
||||||
val chapterName = urlElement.text().substringAfter("НОВЕ").trim()
|
val chapterName = urlElement.text().substringAfter("НОВЕ").trim()
|
||||||
val chapterNumber = urlElement.text().substringAfter("Розділ").substringBefore("-").trim()
|
val chapterNumber = urlElement.text().substringAfter("Розділ").substringBefore("-").trim()
|
||||||
|
@ -137,31 +146,58 @@ class Mangainua : ParsedHttpSource() {
|
||||||
chapter_number = chapterNumber.toFloat()
|
chapter_number = chapterNumber.toFloat()
|
||||||
previousChapterNumber = chapterNumber.toFloat()
|
previousChapterNumber = chapterNumber.toFloat()
|
||||||
}
|
}
|
||||||
|
date_upload = dateFormat.parse(element.child(0).ownText())?.time!!
|
||||||
}
|
}
|
||||||
date_upload = parseDate(element.select("div.ltcright:containsOwn(.)").text())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
override fun chapterListParse(response: Response): List<SChapter> {
|
override fun chapterListParse(response: Response): List<SChapter> {
|
||||||
return super.chapterListParse(response).reversed()
|
val document = response.asJsoup()
|
||||||
|
val userHash = document.parseUserHash()
|
||||||
|
val metaElement = document.selectFirst(Evaluator.Id("linkstocomics"))!!
|
||||||
|
val body = FormBody.Builder()
|
||||||
|
.addEncoded("action", "show")
|
||||||
|
.addEncoded("news_id", metaElement.attr("data-news_id"))
|
||||||
|
.addEncoded("news_category", metaElement.attr("data-news_category"))
|
||||||
|
.addEncoded("this_link", metaElement.attr("data-this_link"))
|
||||||
|
.addEncoded("user_hash", userHash)
|
||||||
|
.build()
|
||||||
|
val request = POST("$baseUrl/engine/ajax/controller.php?mod=load_chapters", headers, body)
|
||||||
|
val chaptersHtml = client.newCall(request).execute().body.string()
|
||||||
|
val chaptersDocument = Jsoup.parseBodyFragment(chaptersHtml)
|
||||||
|
return parseChapterElements(chaptersDocument.body().children()).asReversed()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pages
|
// Pages
|
||||||
override fun pageListParse(document: Document): List<Page> {
|
override fun pageListParse(document: Document): List<Page> {
|
||||||
return document.select("ul.loadcomicsimages img").mapIndexed { i, element ->
|
val userHash = document.parseUserHash()
|
||||||
Page(i, "", element.attr("abs:data-src"))
|
val newsId = document.selectFirst(Evaluator.Id("comics"))!!.attr("data-news_id")
|
||||||
|
val url = "$baseUrl/engine/ajax/controller.php?mod=load_chapters_image&news_id=$newsId&action=show&user_hash=$userHash"
|
||||||
|
val pagesHtml = client.newCall(GET(url, headers)).execute().body.string()
|
||||||
|
val pagesDocument = Jsoup.parseBodyFragment(pagesHtml)
|
||||||
|
return pagesDocument.getElementsByTag("img").mapIndexed { index, img ->
|
||||||
|
Page(index, imageUrl = img.attr("data-src"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used")
|
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException()
|
||||||
|
|
||||||
private fun parseDate(dateStr: String): Long {
|
|
||||||
return runCatching { DATE_FORMATTER.parse(dateStr)?.time }
|
|
||||||
.getOrNull() ?: 0L
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val DATE_FORMATTER by lazy {
|
private val DATE_FORMATTER by lazy {
|
||||||
SimpleDateFormat("dd.MM.yyyy", Locale.ENGLISH)
|
SimpleDateFormat("dd.MM.yyyy", Locale.ENGLISH)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun Document.parseUserHash(): String {
|
||||||
|
val start = "site_login_hash = '"
|
||||||
|
for (element in body().children()) {
|
||||||
|
if (element.tagName() != "script") continue
|
||||||
|
val data = element.data()
|
||||||
|
val leftIndex = data.indexOf(start)
|
||||||
|
if (leftIndex == -1) continue
|
||||||
|
val startIndex = leftIndex + start.length
|
||||||
|
val endIndex = data.indexOf('\'', startIndex)
|
||||||
|
return data.substring(startIndex, endIndex)
|
||||||
|
}
|
||||||
|
throw Exception("Couldn't find user hash")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|