MangaPark: fixes & improvements (#8483)
* cover absolute url & uncensored cover for hentai * utils * nsfw pref and thumbnail baseurl * lint * try upload status when original status is unknown * include extra info in description * off by default * bump * clean title * nullable * status set using nullability * review changes * revert * actually set to off
This commit is contained in:
parent
1393a25fbb
commit
219ceaac1e
@ -1,7 +1,7 @@
|
||||
ext {
|
||||
extName = 'MangaPark'
|
||||
extClass = '.MangaParkFactory'
|
||||
extVersionCode = 21
|
||||
extVersionCode = 22
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
|
@ -17,20 +17,19 @@ import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import keiyoushi.utils.firstInstanceOrNull
|
||||
import keiyoushi.utils.getPreferences
|
||||
import keiyoushi.utils.parseAs
|
||||
import keiyoushi.utils.toJsonString
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okhttp3.Response
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
@ -54,13 +53,15 @@ class MangaPark(
|
||||
|
||||
private val apiUrl = "$baseUrl/apo/"
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
override val client = network.cloudflareClient.newBuilder()
|
||||
.addInterceptor(::siteSettingsInterceptor)
|
||||
.addNetworkInterceptor(CookieInterceptor(domain, "nsfw" to "2"))
|
||||
.rateLimitHost(apiUrl.toHttpUrl(), 1)
|
||||
.build()
|
||||
override val client = network.cloudflareClient.newBuilder().apply {
|
||||
if (preference.getBoolean(ENABLE_NSFW, true)) {
|
||||
addInterceptor(::siteSettingsInterceptor)
|
||||
addNetworkInterceptor(CookieInterceptor(domain, "nsfw" to "2"))
|
||||
}
|
||||
rateLimitHost(apiUrl.toHttpUrl(), 1)
|
||||
// intentionally after rate limit interceptor so thumbnails are not rate limited
|
||||
addInterceptor(::thumbnailDomainInterceptor)
|
||||
}.build()
|
||||
|
||||
override fun headersBuilder() = super.headersBuilder()
|
||||
.set("Referer", "$baseUrl/")
|
||||
@ -96,8 +97,10 @@ class MangaPark(
|
||||
|
||||
override fun searchMangaParse(response: Response): MangasPage {
|
||||
val result = response.parseAs<SearchResponse>()
|
||||
val pageAsCover = preference.getString(UNCENSORED_COVER_PREF, "off")!!
|
||||
val shortenTitle = preference.getBoolean(SHORTEN_TITLE_PREF, false)
|
||||
|
||||
val entries = result.data.searchComics.items.map { it.data.toSManga() }
|
||||
val entries = result.data.searchComics.items.map { it.data.toSManga(shortenTitle, pageAsCover) }
|
||||
val hasNextPage = entries.size == size
|
||||
|
||||
return MangasPage(entries, hasNextPage)
|
||||
@ -164,8 +167,10 @@ class MangaPark(
|
||||
|
||||
override fun mangaDetailsParse(response: Response): SManga {
|
||||
val result = response.parseAs<DetailsResponse>()
|
||||
val pageAsCover = preference.getString(UNCENSORED_COVER_PREF, "off")!!
|
||||
val shortenTitle = preference.getBoolean(SHORTEN_TITLE_PREF, false)
|
||||
|
||||
return result.data.comic.data.toSManga()
|
||||
return result.data.comic.data.toSManga(shortenTitle, pageAsCover)
|
||||
}
|
||||
|
||||
override fun getMangaUrl(manga: SManga) = baseUrl + manga.url.substringBeforeLast("#")
|
||||
@ -220,7 +225,7 @@ class MangaPark(
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, _ ->
|
||||
Toast.makeText(screen.context, "Restart Tachiyomi to apply changes", Toast.LENGTH_LONG).show()
|
||||
Toast.makeText(screen.context, "Restart the app to apply changes", Toast.LENGTH_LONG).show()
|
||||
true
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
@ -231,16 +236,34 @@ class MangaPark(
|
||||
summary = "Refresh chapter list to apply changes"
|
||||
setDefaultValue(false)
|
||||
}.also(screen::addPreference)
|
||||
|
||||
SwitchPreferenceCompat(screen.context).apply {
|
||||
key = ENABLE_NSFW
|
||||
title = "Enable NSFW content"
|
||||
summary = "Clear Cookies & Restart the app to apply changes."
|
||||
setDefaultValue(true)
|
||||
}.also(screen::addPreference)
|
||||
|
||||
SwitchPreferenceCompat(screen.context).apply {
|
||||
key = SHORTEN_TITLE_PREF
|
||||
title = "Remove extra information from title"
|
||||
summary = "Clear database to apply changes\n\n" +
|
||||
"Note: doesn't not work for entries in library"
|
||||
setDefaultValue(false)
|
||||
}.also(screen::addPreference)
|
||||
|
||||
ListPreference(screen.context).apply {
|
||||
key = UNCENSORED_COVER_PREF
|
||||
title = "Attempt to use Uncensored Cover for Hentai"
|
||||
summary = "Uses first or last chapter page as cover"
|
||||
entries = arrayOf("Off", "First Chapter", "Last Chapter")
|
||||
entryValues = arrayOf("off", "first", "last")
|
||||
setDefaultValue("off")
|
||||
}.also(screen::addPreference)
|
||||
}
|
||||
|
||||
private inline fun <reified T> Response.parseAs(): T =
|
||||
use { body.string() }.let(json::decodeFromString)
|
||||
|
||||
private inline fun <reified T> List<*>.firstInstanceOrNull(): T? =
|
||||
filterIsInstance<T>().firstOrNull()
|
||||
|
||||
private inline fun <reified T : Any> T.toJsonRequestBody() =
|
||||
json.encodeToString(this).toRequestBody(JSON_MEDIA_TYPE)
|
||||
toJsonString().toRequestBody(JSON_MEDIA_TYPE)
|
||||
|
||||
private val cookiesNotSet = AtomicBoolean(true)
|
||||
private val latch = CountDownLatch(1)
|
||||
@ -271,6 +294,25 @@ class MangaPark(
|
||||
return chain.proceed(request)
|
||||
}
|
||||
|
||||
private fun thumbnailDomainInterceptor(chain: Interceptor.Chain): Response {
|
||||
val request = chain.request()
|
||||
val url = request.url
|
||||
|
||||
return if (url.host == THUMBNAIL_LOOPBACK_HOST) {
|
||||
val newUrl = url.newBuilder()
|
||||
.host(domain)
|
||||
.build()
|
||||
|
||||
val newRequest = request.newBuilder()
|
||||
.url(newUrl)
|
||||
.build()
|
||||
|
||||
chain.proceed(newRequest)
|
||||
} else {
|
||||
chain.proceed(request)
|
||||
}
|
||||
}
|
||||
|
||||
override fun imageUrlParse(response: Response): String {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
@ -298,6 +340,11 @@ class MangaPark(
|
||||
"mpark.to",
|
||||
)
|
||||
|
||||
private const val ENABLE_NSFW = "pref_nsfw"
|
||||
private const val DUPLICATE_CHAPTER_PREF_KEY = "pref_dup_chapters"
|
||||
private const val SHORTEN_TITLE_PREF = "pref_shorten_title"
|
||||
private const val UNCENSORED_COVER_PREF = "pref_uncensored_cover"
|
||||
}
|
||||
}
|
||||
|
||||
const val THUMBNAIL_LOOPBACK_HOST = "127.0.0.1"
|
||||
|
@ -38,33 +38,68 @@ class MangaParkComic(
|
||||
private val originalStatus: String? = null,
|
||||
private val uploadStatus: String? = null,
|
||||
private val summary: String? = null,
|
||||
private val extraInfo: String? = null,
|
||||
@SerialName("urlCoverOri") private val cover: String? = null,
|
||||
private val urlPath: String,
|
||||
@SerialName("max_chapterNode") private val latestChapter: Data<ImageFiles>? = null,
|
||||
@SerialName("first_chapterNode") private val firstChapter: Data<ImageFiles>? = null,
|
||||
) {
|
||||
fun toSManga() = SManga.create().apply {
|
||||
fun toSManga(shortenTitle: Boolean, pageAsCover: String) = SManga.create().apply {
|
||||
url = "$urlPath#$id"
|
||||
title = name
|
||||
thumbnail_url = cover
|
||||
title = if (shortenTitle) {
|
||||
var shortName = name
|
||||
while (shortenTitleRegex.containsMatchIn(shortName)) {
|
||||
shortName = shortName.replace(shortenTitleRegex, "").trim()
|
||||
}
|
||||
|
||||
shortName
|
||||
} else {
|
||||
name
|
||||
}
|
||||
thumbnail_url = run {
|
||||
val coverUrl = cover?.let {
|
||||
when {
|
||||
it.startsWith("http") -> it
|
||||
it.startsWith("/") -> "https://$THUMBNAIL_LOOPBACK_HOST$it"
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
if (pageAsCover != "off" && useLatestPageAsCover(genres)) {
|
||||
if (pageAsCover == "first") {
|
||||
firstChapter?.data?.imageFile?.urlList?.firstOrNull() ?: coverUrl
|
||||
} else {
|
||||
latestChapter?.data?.imageFile?.urlList?.firstOrNull() ?: coverUrl
|
||||
}
|
||||
} else {
|
||||
coverUrl
|
||||
}
|
||||
}
|
||||
author = authors?.joinToString()
|
||||
artist = artists?.joinToString()
|
||||
description = buildString {
|
||||
val desc = summary?.let { Jsoup.parse(it).text() }
|
||||
val names = altNames?.takeUnless { it.isEmpty() }
|
||||
?.joinToString("\n") { "• ${it.trim()}" }
|
||||
|
||||
if (desc.isNullOrEmpty()) {
|
||||
if (!names.isNullOrEmpty()) {
|
||||
append("Alternative Names:\n", names)
|
||||
}
|
||||
} else {
|
||||
append(desc)
|
||||
if (!names.isNullOrEmpty()) {
|
||||
append("\n\nAlternative Names:\n", names)
|
||||
}
|
||||
if (shortenTitle) {
|
||||
append(name)
|
||||
append("\n\n")
|
||||
}
|
||||
}
|
||||
summary?.also {
|
||||
append(Jsoup.parse(it).wholeText().trim())
|
||||
append("\n\n")
|
||||
}
|
||||
extraInfo?.takeUnless(String::isBlank)?.also {
|
||||
append("Extra Info:\n")
|
||||
append(Jsoup.parse(it).wholeText().trim())
|
||||
append("\n\n")
|
||||
}
|
||||
altNames?.takeUnless(List<String>::isEmpty)
|
||||
?.joinToString(
|
||||
prefix = "Alternative Names:\n",
|
||||
separator = "\n",
|
||||
) { "• ${it.trim()}" }
|
||||
?.also(::append)
|
||||
}.trim()
|
||||
genre = genres?.joinToString { it.replace("_", " ").toCamelCase() }
|
||||
status = when (originalStatus) {
|
||||
status = when (originalStatus ?: uploadStatus) {
|
||||
"ongoing" -> SManga.ONGOING
|
||||
"completed" -> {
|
||||
if (uploadStatus == "ongoing") {
|
||||
@ -96,6 +131,14 @@ class MangaParkComic(
|
||||
}
|
||||
return result.toString()
|
||||
}
|
||||
|
||||
private fun useLatestPageAsCover(genres: List<String>?): Boolean {
|
||||
return genres.orEmpty().let {
|
||||
it.contains("hentai") && !it.contains("webtoon")
|
||||
}
|
||||
}
|
||||
|
||||
private val shortenTitleRegex = Regex("""^(\[[^]]+\])|^(\([^)]+\))|^(\{[^}]+\})|(\[[^]]+\])${'$'}|(\([^)]+\))${'$'}|(\{[^}]+\})${'$'}""")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,8 +25,23 @@ val SEARCH_QUERY = buildQuery {
|
||||
originalStatus
|
||||
uploadStatus
|
||||
summary
|
||||
extraInfo
|
||||
urlCoverOri
|
||||
urlPath
|
||||
max_chapterNode {
|
||||
data {
|
||||
imageFile {
|
||||
urlList
|
||||
}
|
||||
}
|
||||
}
|
||||
first_chapterNode {
|
||||
data {
|
||||
imageFile {
|
||||
urlList
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -52,8 +67,23 @@ val DETAILS_QUERY = buildQuery {
|
||||
originalStatus
|
||||
uploadStatus
|
||||
summary
|
||||
extraInfo
|
||||
urlCoverOri
|
||||
urlPath
|
||||
max_chapterNode {
|
||||
data {
|
||||
imageFile {
|
||||
urlList
|
||||
}
|
||||
}
|
||||
}
|
||||
first_chapterNode {
|
||||
data {
|
||||
imageFile {
|
||||
urlList
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user