Mangapark: duplicate chapters & unblock site blocked genres (#3979)

* fetch duplicate chapters

+ small refactor
  => data class -> class

* site settings to not block "Hentai" genre

* user name as scanlator for first choice

* forgor

* try not to make duplicate calls

concurrency my beloved

* move fetch genre call to `getFilterList`
This commit is contained in:
AwkwardPeak7 2024-07-13 11:12:21 +05:00 committed by Draff
parent 33afc95944
commit d11495d8a1
No known key found for this signature in database
GPG Key ID: E8A89F3211677653
5 changed files with 131 additions and 48 deletions

View File

@ -1,7 +1,7 @@
ext {
extName = 'MangaPark'
extClass = '.MangaParkFactory'
extVersionCode = 19
extVersionCode = 20
isNsfw = true
}

View File

@ -4,6 +4,7 @@ import android.app.Application
import android.widget.Toast
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreferenceCompat
import eu.kanade.tachiyomi.lib.cookieinterceptor.CookieInterceptor
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
@ -17,10 +18,14 @@ 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 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
@ -28,6 +33,8 @@ import okhttp3.Response
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.util.concurrent.CountDownLatch
import java.util.concurrent.atomic.AtomicBoolean
class MangaPark(
override val lang: String,
@ -53,6 +60,7 @@ class MangaPark(
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()
@ -90,8 +98,6 @@ class MangaPark(
}
override fun searchMangaParse(response: Response): MangasPage {
runCatching(::getGenres)
val result = response.parseAs<SearchResponse>()
val entries = result.data.searchComics.items.map { it.data.toSManga() }
@ -126,6 +132,10 @@ class MangaPark(
}
override fun getFilterList(): FilterList {
CoroutineScope(Dispatchers.IO).launch {
runCatching(::getGenres)
}
val filters = mutableListOf<Filter<*>>(
SortFilter(),
OriginalLanguageFilter(),
@ -175,7 +185,13 @@ class MangaPark(
override fun chapterListParse(response: Response): List<SChapter> {
val result = response.parseAs<ChapterListResponse>()
return result.data.chapterList.map { it.data.toSChapter() }.reversed()
return if (preference.getBoolean(DUPLICATE_CHAPTER_PREF_KEY, false)) {
result.data.chapterList.flatMap {
it.data.dupChapters.map { it.data.toSChapter() }
}.reversed()
} else {
result.data.chapterList.map { it.data.toSChapter() }.reversed()
}
}
override fun getChapterUrl(chapter: SChapter) = baseUrl + chapter.url.substringBeforeLast("#")
@ -211,6 +227,13 @@ class MangaPark(
true
}
}.also(screen::addPreference)
SwitchPreferenceCompat(screen.context).apply {
key = DUPLICATE_CHAPTER_PREF_KEY
title = "Fetch Duplicate Chapters"
summary = "Refresh chapter list to apply changes"
setDefaultValue(false)
}.also(screen::addPreference)
}
private inline fun <reified T> Response.parseAs(): T =
@ -222,6 +245,35 @@ class MangaPark(
private inline fun <reified T : Any> T.toJsonRequestBody() =
json.encodeToString(this).toRequestBody(JSON_MEDIA_TYPE)
private val cookiesNotSet = AtomicBoolean(true)
private val latch = CountDownLatch(1)
// sets necessary cookies to not block genres like `Hentai`
private fun siteSettingsInterceptor(chain: Interceptor.Chain): Response {
val request = chain.request()
val settingsUrl = "$baseUrl/aok/settings-save"
if (
request.url.toString() != settingsUrl &&
request.url.host == domain
) {
if (cookiesNotSet.getAndSet(false)) {
val payload =
"""{"data":{"general_autoLangs":[],"general_userLangs":[],"general_excGenres":[],"general_prefLangs":[]}}"""
.toRequestBody(JSON_MEDIA_TYPE)
client.newCall(POST(settingsUrl, headers, payload)).execute().close()
latch.countDown()
} else {
latch.await()
}
}
return chain.proceed(request)
}
override fun imageUrlParse(response: Response): String {
throw UnsupportedOperationException()
}
@ -248,5 +300,7 @@ class MangaPark(
"parkmanga.org",
"mpark.to",
)
private const val DUPLICATE_CHAPTER_PREF_KEY = "pref_dup_chapters"
}
}

View File

@ -12,34 +12,34 @@ typealias ChapterListResponse = Data<ChapterList>
typealias PageListResponse = Data<ChapterPages>
@Serializable
data class Data<T>(val data: T)
class Data<T>(val data: T)
@Serializable
data class Items<T>(val items: List<T>)
class Items<T>(val items: List<T>)
@Serializable
data class SearchComics(
class SearchComics(
@SerialName("get_searchComic") val searchComics: Items<Data<MangaParkComic>>,
)
@Serializable
data class ComicNode(
class ComicNode(
@SerialName("get_comicNode") val comic: Data<MangaParkComic>,
)
@Serializable
data class MangaParkComic(
val id: String,
val name: String,
val altNames: List<String>? = null,
val authors: List<String>? = null,
val artists: List<String>? = null,
val genres: List<String>? = null,
val originalStatus: String? = null,
val uploadStatus: String? = null,
val summary: String? = null,
@SerialName("urlCoverOri") val cover: String? = null,
val urlPath: String,
class MangaParkComic(
private val id: String,
private val name: String,
private val altNames: List<String>? = null,
private val authors: List<String>? = null,
private val artists: List<String>? = null,
private val genres: List<String>? = null,
private val originalStatus: String? = null,
private val uploadStatus: String? = null,
private val summary: String? = null,
@SerialName("urlCoverOri") private val cover: String? = null,
private val urlPath: String,
) {
fun toSManga() = SManga.create().apply {
url = "$urlPath#$id"
@ -100,18 +100,21 @@ data class MangaParkComic(
}
@Serializable
data class ChapterList(
class ChapterList(
@SerialName("get_comicChapterList") val chapterList: List<Data<MangaParkChapter>>,
)
@Serializable
data class MangaParkChapter(
val id: String,
@SerialName("dname") val displayName: String,
val title: String? = null,
val dateCreate: Long? = null,
val dateModify: Long? = null,
val urlPath: String,
class MangaParkChapter(
private val id: String,
@SerialName("dname") private val displayName: String,
private val title: String? = null,
private val dateCreate: Long? = null,
private val dateModify: Long? = null,
private val urlPath: String,
private val srcTitle: String? = null,
private val userNode: Data<Name>? = null,
val dupChapters: List<Data<MangaParkChapter>> = emptyList(),
) {
fun toSChapter() = SChapter.create().apply {
url = "$urlPath#$id"
@ -120,20 +123,24 @@ data class MangaParkChapter(
title?.let { append(": ", it) }
}
date_upload = dateModify ?: dateCreate ?: 0L
scanlator = userNode?.data?.name ?: srcTitle ?: "Unknown"
}
}
@Serializable
data class ChapterPages(
class Name(val name: String)
@Serializable
class ChapterPages(
@SerialName("get_chapterNode") val chapterPages: Data<ImageFiles>,
)
@Serializable
data class ImageFiles(
class ImageFiles(
val imageFile: UrlList,
)
@Serializable
data class UrlList(
class UrlList(
val urlList: List<String>,
)

View File

@ -4,28 +4,28 @@ import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class GraphQL<T>(
val variables: T,
val query: String,
class GraphQL<T>(
private val variables: T,
private val query: String,
)
@Serializable
data class SearchVariables(val select: SearchPayload)
class SearchVariables(private val select: SearchPayload)
@Serializable
data class SearchPayload(
@SerialName("word") val query: String? = null,
val incGenres: List<String>? = null,
val excGenres: List<String>? = null,
val incTLangs: List<String>? = null,
val incOLangs: List<String>? = null,
val sortby: String? = null,
val chapCount: String? = null,
val origStatus: String? = null,
val siteStatus: String? = null,
val page: Int,
val size: Int,
class SearchPayload(
@SerialName("word") private val query: String? = null,
private val incGenres: List<String>? = null,
private val excGenres: List<String>? = null,
private val incTLangs: List<String>? = null,
private val incOLangs: List<String>? = null,
private val sortby: String? = null,
private val chapCount: String? = null,
private val origStatus: String? = null,
private val siteStatus: String? = null,
private val page: Int,
private val size: Int,
)
@Serializable
data class IdVariables(val id: String)
class IdVariables(private val id: String)

View File

@ -75,6 +75,28 @@ val CHAPTERS_QUERY = buildQuery {
dateModify
dateCreate
urlPath
srcTitle
userNode {
data {
name
}
}
dupChapters {
data {
id
dname
title
dateModify
dateCreate
urlPath
srcTitle
userNode {
data {
name
}
}
}
}
}
}
}