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:
parent
33afc95944
commit
d11495d8a1
@ -1,7 +1,7 @@
|
|||||||
ext {
|
ext {
|
||||||
extName = 'MangaPark'
|
extName = 'MangaPark'
|
||||||
extClass = '.MangaParkFactory'
|
extClass = '.MangaParkFactory'
|
||||||
extVersionCode = 19
|
extVersionCode = 20
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>,
|
||||||
)
|
)
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user