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