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 { ext {
extName = 'MangaPark' extName = 'MangaPark'
extClass = '.MangaParkFactory' extClass = '.MangaParkFactory'
extVersionCode = 19 extVersionCode = 20
isNsfw = true isNsfw = true
} }

View File

@ -4,6 +4,7 @@ import android.app.Application
import android.widget.Toast import android.widget.Toast
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreferenceCompat
import eu.kanade.tachiyomi.lib.cookieinterceptor.CookieInterceptor import eu.kanade.tachiyomi.lib.cookieinterceptor.CookieInterceptor
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST 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.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.asJsoup 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.decodeFromString
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
@ -28,6 +33,8 @@ import okhttp3.Response
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.util.concurrent.CountDownLatch
import java.util.concurrent.atomic.AtomicBoolean
class MangaPark( class MangaPark(
override val lang: String, override val lang: String,
@ -53,6 +60,7 @@ class MangaPark(
private val json: Json by injectLazy() private val json: Json by injectLazy()
override val client = network.cloudflareClient.newBuilder() override val client = network.cloudflareClient.newBuilder()
.addInterceptor(::siteSettingsInterceptor)
.addNetworkInterceptor(CookieInterceptor(domain, "nsfw" to "2")) .addNetworkInterceptor(CookieInterceptor(domain, "nsfw" to "2"))
.rateLimitHost(apiUrl.toHttpUrl(), 1) .rateLimitHost(apiUrl.toHttpUrl(), 1)
.build() .build()
@ -90,8 +98,6 @@ class MangaPark(
} }
override fun searchMangaParse(response: Response): MangasPage { override fun searchMangaParse(response: Response): MangasPage {
runCatching(::getGenres)
val result = response.parseAs<SearchResponse>() val result = response.parseAs<SearchResponse>()
val entries = result.data.searchComics.items.map { it.data.toSManga() } val entries = result.data.searchComics.items.map { it.data.toSManga() }
@ -126,6 +132,10 @@ class MangaPark(
} }
override fun getFilterList(): FilterList { override fun getFilterList(): FilterList {
CoroutineScope(Dispatchers.IO).launch {
runCatching(::getGenres)
}
val filters = mutableListOf<Filter<*>>( val filters = mutableListOf<Filter<*>>(
SortFilter(), SortFilter(),
OriginalLanguageFilter(), OriginalLanguageFilter(),
@ -175,7 +185,13 @@ class MangaPark(
override fun chapterListParse(response: Response): List<SChapter> { override fun chapterListParse(response: Response): List<SChapter> {
val result = response.parseAs<ChapterListResponse>() 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("#") override fun getChapterUrl(chapter: SChapter) = baseUrl + chapter.url.substringBeforeLast("#")
@ -211,6 +227,13 @@ class MangaPark(
true true
} }
}.also(screen::addPreference) }.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 = private inline fun <reified T> Response.parseAs(): T =
@ -222,6 +245,35 @@ class MangaPark(
private inline fun <reified T : Any> T.toJsonRequestBody() = private inline fun <reified T : Any> T.toJsonRequestBody() =
json.encodeToString(this).toRequestBody(JSON_MEDIA_TYPE) 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 { override fun imageUrlParse(response: Response): String {
throw UnsupportedOperationException() throw UnsupportedOperationException()
} }
@ -248,5 +300,7 @@ class MangaPark(
"parkmanga.org", "parkmanga.org",
"mpark.to", "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> typealias PageListResponse = Data<ChapterPages>
@Serializable @Serializable
data class Data<T>(val data: T) class Data<T>(val data: T)
@Serializable @Serializable
data class Items<T>(val items: List<T>) class Items<T>(val items: List<T>)
@Serializable @Serializable
data class SearchComics( class SearchComics(
@SerialName("get_searchComic") val searchComics: Items<Data<MangaParkComic>>, @SerialName("get_searchComic") val searchComics: Items<Data<MangaParkComic>>,
) )
@Serializable @Serializable
data class ComicNode( class ComicNode(
@SerialName("get_comicNode") val comic: Data<MangaParkComic>, @SerialName("get_comicNode") val comic: Data<MangaParkComic>,
) )
@Serializable @Serializable
data class MangaParkComic( class MangaParkComic(
val id: String, private val id: String,
val name: String, private val name: String,
val altNames: List<String>? = null, private val altNames: List<String>? = null,
val authors: List<String>? = null, private val authors: List<String>? = null,
val artists: List<String>? = null, private val artists: List<String>? = null,
val genres: List<String>? = null, private val genres: List<String>? = null,
val originalStatus: String? = null, private val originalStatus: String? = null,
val uploadStatus: String? = null, private val uploadStatus: String? = null,
val summary: String? = null, private val summary: String? = null,
@SerialName("urlCoverOri") val cover: String? = null, @SerialName("urlCoverOri") private val cover: String? = null,
val urlPath: String, private val urlPath: String,
) { ) {
fun toSManga() = SManga.create().apply { fun toSManga() = SManga.create().apply {
url = "$urlPath#$id" url = "$urlPath#$id"
@ -100,18 +100,21 @@ data class MangaParkComic(
} }
@Serializable @Serializable
data class ChapterList( class ChapterList(
@SerialName("get_comicChapterList") val chapterList: List<Data<MangaParkChapter>>, @SerialName("get_comicChapterList") val chapterList: List<Data<MangaParkChapter>>,
) )
@Serializable @Serializable
data class MangaParkChapter( class MangaParkChapter(
val id: String, private val id: String,
@SerialName("dname") val displayName: String, @SerialName("dname") private val displayName: String,
val title: String? = null, private val title: String? = null,
val dateCreate: Long? = null, private val dateCreate: Long? = null,
val dateModify: Long? = null, private val dateModify: Long? = null,
val urlPath: String, 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 { fun toSChapter() = SChapter.create().apply {
url = "$urlPath#$id" url = "$urlPath#$id"
@ -120,20 +123,24 @@ data class MangaParkChapter(
title?.let { append(": ", it) } title?.let { append(": ", it) }
} }
date_upload = dateModify ?: dateCreate ?: 0L date_upload = dateModify ?: dateCreate ?: 0L
scanlator = userNode?.data?.name ?: srcTitle ?: "Unknown"
} }
} }
@Serializable @Serializable
data class ChapterPages( class Name(val name: String)
@Serializable
class ChapterPages(
@SerialName("get_chapterNode") val chapterPages: Data<ImageFiles>, @SerialName("get_chapterNode") val chapterPages: Data<ImageFiles>,
) )
@Serializable @Serializable
data class ImageFiles( class ImageFiles(
val imageFile: UrlList, val imageFile: UrlList,
) )
@Serializable @Serializable
data class UrlList( class UrlList(
val urlList: List<String>, val urlList: List<String>,
) )

View File

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

View File

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