beerpsi a95fa2dd5c Make ManhwaZ a multisrc + Add UmeTruyen (#1495)
* Make ManhwaZ a multisrc + Add UmeTruyen

* Forgot to commit the most important stuff

* icons

* Update src/en/manhwaz/build.gradle

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>

* Don't use GlobalScope

* Remove useless optin

* Add CoroutineScope import

---------

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
2024-02-27 20:53:49 +00:00

276 lines
10 KiB
Kotlin

package eu.kanade.tachiyomi.multisrc.manhwaz
import android.util.Log
import eu.kanade.tachiyomi.lib.i18n.Intl
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import java.util.Calendar
abstract class ManhwaZ(
override val name: String,
override val baseUrl: String,
final override val lang: String,
private val mangaDetailsAuthorHeading: String = "author(s)",
private val mangaDetailsStatusHeading: String = "status",
) : ParsedHttpSource() {
override val supportsLatest = true
override val client = network.cloudflareClient
override fun headersBuilder() = super.headersBuilder()
.add("Origin", baseUrl)
.add("Referer", "$baseUrl/")
protected val intl = Intl(
lang,
setOf("en", "vi"),
"en",
this::class.java.classLoader!!,
)
override fun popularMangaRequest(page: Int) = GET(baseUrl, headers)
override fun popularMangaSelector() = "#slide-top > .item"
override fun popularMangaFromElement(element: Element) = SManga.create().apply {
element.selectFirst(".info-item a")!!.let {
title = it.text()
setUrlWithoutDomain(it.attr("href"))
}
thumbnail_url = element.selectFirst(".img-item img")?.imgAttr()
}
override fun popularMangaNextPageSelector(): String? = null
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/?page=$page", headers)
override fun latestUpdatesSelector() = ".page-item-detail"
override fun latestUpdatesFromElement(element: Element) = SManga.create().apply {
element.selectFirst(".item-summary a")!!.let {
title = it.text()
setUrlWithoutDomain(it.attr("href"))
}
thumbnail_url = element.selectFirst(".item-thumb img")?.imgAttr()
}
override fun latestUpdatesNextPageSelector(): String? = "ul.pager a[rel=next]"
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
if (query.isNotEmpty()) {
val url = baseUrl.toHttpUrl().newBuilder().apply {
addPathSegment("search")
addQueryParameter("s", query)
addQueryParameter("page", page.toString())
}.build()
return GET(url, headers)
}
val url = baseUrl.toHttpUrl().newBuilder().apply {
val filterList = filters.ifEmpty { getFilterList() }
val genreFilter = filterList.find { it is GenreFilter } as? GenreFilter
val orderByFilter = filterList.find { it is OrderByFilter } as? OrderByFilter
val genreId = genreFilter?.options?.get(genreFilter.state)?.id
if (genreFilter != null && genreFilter.state != 0) {
addPathSegments(genreId!!)
}
// Can't sort in "All" or "Completed"
if (orderByFilter != null && genreId?.startsWith("genre/") == true) {
addQueryParameter(
"m_orderby",
orderByFilter.options[orderByFilter.state].id,
)
}
addQueryParameter("page", page.toString())
}.build()
return GET(url, headers)
}
override fun searchMangaSelector() = latestUpdatesSelector()
override fun searchMangaFromElement(element: Element) = latestUpdatesFromElement(element)
override fun searchMangaNextPageSelector(): String? = latestUpdatesNextPageSelector()
private val ongoingStatusList = listOf("ongoing", "đang ra")
private val completedStatusList = listOf("completed", "hoàn thành")
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
val statusText = document.selectFirst("div.summary-heading:contains($mangaDetailsStatusHeading) + div.summary-content")
?.text()
?.lowercase()
?: ""
title = document.selectFirst("div.post-title h1")!!.text()
author = document.selectFirst("div.summary-heading:contains($mangaDetailsAuthorHeading) + div.summary-content")?.text()
description = document.selectFirst("div.summary__content")?.text()
genre = document.select("div.genres-content a[rel=tag]").joinToString { it.text() }
status = when {
ongoingStatusList.contains(statusText) -> SManga.ONGOING
completedStatusList.contains(statusText) -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
thumbnail_url = document.selectFirst("div.summary_image img")?.imgAttr()
}
override fun chapterListSelector() = "li.wp-manga-chapter"
override fun chapterFromElement(element: Element) = SChapter.create().apply {
element.selectFirst("a")!!.let {
setUrlWithoutDomain(it.attr("href"))
name = it.text()
}
element.selectFirst("span.chapter-release-date")?.text()?.let {
date_upload = parseRelativeDate(it)
}
}
override fun pageListParse(document: Document) =
document.select("div.page-break img").mapIndexed { i, it ->
Page(i, imageUrl = it.imgAttr())
}
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException()
override fun getFilterList(): FilterList {
fetchGenreList()
val filters = buildList {
add(Filter.Header(intl["filter_ignored_warning"]))
add(Filter.Header(intl.format("cannot_use_order_by_warning", intl["genre_all"], intl["genre_completed"])))
if (fetchGenreStatus == FetchGenreStatus.NOT_FETCHED && fetchGenreAttempts >= 3) {
add(Filter.Header(intl["genre_fetch_failed"]))
} else if (fetchGenreStatus != FetchGenreStatus.FETCHED) {
add(Filter.Header(intl["genre_missing_warning"]))
}
add(Filter.Separator())
if (genres.isNotEmpty()) {
add(GenreFilter(intl, genres))
}
add(OrderByFilter(intl))
}
return FilterList(filters)
}
private class GenreFilter(
intl: Intl,
genres: List<SelectOption>,
) : SelectFilter(intl["genre_filter_title"], genres)
private class OrderByFilter(intl: Intl) : SelectFilter(
intl["order_by_filter_title"],
listOf(
SelectOption(intl["order_by_latest"], "latest"),
SelectOption(intl["order_by_rating"], "rating"),
SelectOption(intl["order_by_most_views"], "views"),
SelectOption(intl["order_by_new"], "new"),
),
)
private var genres = emptyList<SelectOption>()
private var fetchGenreStatus = FetchGenreStatus.NOT_FETCHED
private var fetchGenreAttempts = 0
private val scope = CoroutineScope(Dispatchers.IO)
private fun fetchGenreList() {
if (fetchGenreStatus != FetchGenreStatus.NOT_FETCHED || fetchGenreAttempts >= 3) {
return
}
fetchGenreStatus = FetchGenreStatus.FETCHING
fetchGenreAttempts++
scope.launch {
try {
val document = client.newCall(GET("$baseUrl/genre")).await().asJsoup()
genres = buildList {
add(SelectOption(intl["genre_all"], ""))
add(SelectOption(intl["genre_completed"], "completed"))
document.select("ul.page-genres li a").forEach {
val path = it.absUrl("href").toHttpUrl().encodedPath.removePrefix("/")
add(SelectOption(it.ownText(), path))
}
}
fetchGenreStatus = FetchGenreStatus.FETCHED
} catch (e: Exception) {
Log.e("ManhwaZ/$name", "Error fetching genres", e)
fetchGenreStatus = FetchGenreStatus.NOT_FETCHED
}
}
}
private enum class FetchGenreStatus { NOT_FETCHED, FETCHED, FETCHING }
private class SelectOption(val name: String, val id: String)
private open class SelectFilter(
name: String,
val options: List<SelectOption>,
) : Filter.Select<String>(name, options.map { it.name }.toTypedArray())
private val secondsUnit = listOf("second", "seconds", "giây")
private val minutesUnit = listOf("minute", "minutes", "phút")
private val hourUnit = listOf("hour", "hours", "giờ")
private val dayUnit = listOf("day", "days", "ngày")
private val weekUnit = listOf("week", "weeks", "tuần")
private val monthUnit = listOf("month", "months", "tháng")
private val yearUnit = listOf("year", "years", "năm")
private fun parseRelativeDate(date: String): Long {
val (valueString, unit) = date.substringBeforeLast(" ").split(" ", limit = 2)
val value = valueString.toInt()
val calendar = Calendar.getInstance().apply {
val field = when {
secondsUnit.contains(unit) -> Calendar.SECOND
minutesUnit.contains(unit) -> Calendar.MINUTE
hourUnit.contains(unit) -> Calendar.HOUR_OF_DAY
dayUnit.contains(unit) -> Calendar.DAY_OF_MONTH
weekUnit.contains(unit) -> Calendar.WEEK_OF_MONTH
monthUnit.contains(unit) -> Calendar.MONTH
yearUnit.contains(unit) -> Calendar.YEAR
else -> return 0L
}
add(field, -value)
}
return calendar.timeInMillis
}
protected fun Element.imgAttr(): String = when {
hasAttr("data-src") -> attr("abs:data-src")
hasAttr("data-lazy-src") -> attr("abs:data-lazy-src")
hasAttr("srcset") -> attr("abs:srcset").substringBefore(" ")
hasAttr("data-cfsrc") -> attr("abs:data-cfsrc")
else -> attr("abs:src")
}
}