diff --git a/src/all/mangapark/AndroidManifest.xml b/src/all/mangapark/AndroidManifest.xml
index 9c5dbd1fb..8072ee00d 100644
--- a/src/all/mangapark/AndroidManifest.xml
+++ b/src/all/mangapark/AndroidManifest.xml
@@ -1,22 +1,2 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/src/all/mangapark/build.gradle b/src/all/mangapark/build.gradle
index 03046c8c2..b57b5c530 100644
--- a/src/all/mangapark/build.gradle
+++ b/src/all/mangapark/build.gradle
@@ -3,15 +3,11 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext {
- extName = 'MangaPark v3'
+ extName = 'MangaPark'
pkgNameSuffix = 'all.mangapark'
extClass = '.MangaParkFactory'
- extVersionCode = 18
+ extVersionCode = 19
isNsfw = true
}
apply from: "$rootDir/common.gradle"
-
-dependencies {
- implementation(project(':lib-cryptoaes'))
-}
\ No newline at end of file
diff --git a/src/all/mangapark/res/mipmap-hdpi/ic_launcher.png b/src/all/mangapark/res/mipmap-hdpi/ic_launcher.png
index d75a3acbc..aa0ab84be 100644
Binary files a/src/all/mangapark/res/mipmap-hdpi/ic_launcher.png and b/src/all/mangapark/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/src/all/mangapark/res/mipmap-mdpi/ic_launcher.png b/src/all/mangapark/res/mipmap-mdpi/ic_launcher.png
index bff559a9a..9c1d1e9cb 100644
Binary files a/src/all/mangapark/res/mipmap-mdpi/ic_launcher.png and b/src/all/mangapark/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/src/all/mangapark/res/mipmap-xhdpi/ic_launcher.png b/src/all/mangapark/res/mipmap-xhdpi/ic_launcher.png
index 5db811547..8119cc79a 100644
Binary files a/src/all/mangapark/res/mipmap-xhdpi/ic_launcher.png and b/src/all/mangapark/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/src/all/mangapark/res/mipmap-xxhdpi/ic_launcher.png b/src/all/mangapark/res/mipmap-xxhdpi/ic_launcher.png
index 764ff8bcb..caf61063c 100644
Binary files a/src/all/mangapark/res/mipmap-xxhdpi/ic_launcher.png and b/src/all/mangapark/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/src/all/mangapark/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/mangapark/res/mipmap-xxxhdpi/ic_launcher.png
index b4f2b0ff2..57272a58f 100644
Binary files a/src/all/mangapark/res/mipmap-xxxhdpi/ic_launcher.png and b/src/all/mangapark/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/src/all/mangapark/res/web_hi_res_512.png b/src/all/mangapark/res/web_hi_res_512.png
index f789273b6..3d37f6a4c 100644
Binary files a/src/all/mangapark/res/web_hi_res_512.png and b/src/all/mangapark/res/web_hi_res_512.png differ
diff --git a/src/all/mangapark/src/eu/kanade/tachiyomi/extension/all/mangapark/CookieInterceptor.kt b/src/all/mangapark/src/eu/kanade/tachiyomi/extension/all/mangapark/CookieInterceptor.kt
new file mode 100644
index 000000000..09a4691b7
--- /dev/null
+++ b/src/all/mangapark/src/eu/kanade/tachiyomi/extension/all/mangapark/CookieInterceptor.kt
@@ -0,0 +1,46 @@
+package eu.kanade.tachiyomi.extension.all.mangapark
+
+import android.util.Log
+import android.webkit.CookieManager
+import okhttp3.Interceptor
+import okhttp3.Response
+
+class CookieInterceptor(
+ private val domain: String,
+ private val key: String,
+ private val value: String,
+) : Interceptor {
+
+ init {
+ val url = "https://$domain/"
+ val cookie = "$key=$value; Domain=$domain; Path=/"
+ setCookie(url, cookie)
+ }
+
+ override fun intercept(chain: Interceptor.Chain): Response {
+ val request = chain.request()
+ if (!request.url.host.endsWith(domain)) return chain.proceed(request)
+
+ val cookie = "$key=$value"
+ val cookieList = request.header("Cookie")?.split("; ") ?: emptyList()
+ if (cookie in cookieList) return chain.proceed(request)
+
+ setCookie("https://$domain/", "$cookie; Domain=$domain; Path=/")
+ val prefix = "$key="
+ val newCookie = buildList(cookieList.size + 1) {
+ cookieList.filterNotTo(this) { it.startsWith(prefix) }
+ add(cookie)
+ }.joinToString("; ")
+ val newRequest = request.newBuilder().header("Cookie", newCookie).build()
+ return chain.proceed(newRequest)
+ }
+
+ private fun setCookie(url: String, value: String) {
+ try {
+ CookieManager.getInstance().setCookie(url, value)
+ } catch (e: Exception) {
+ // Probably running on Tachidesk
+ Log.e("MangaPark", "failed to set cookie", e)
+ }
+ }
+}
diff --git a/src/all/mangapark/src/eu/kanade/tachiyomi/extension/all/mangapark/MangaPark.kt b/src/all/mangapark/src/eu/kanade/tachiyomi/extension/all/mangapark/MangaPark.kt
index 108711b15..affd6491d 100644
--- a/src/all/mangapark/src/eu/kanade/tachiyomi/extension/all/mangapark/MangaPark.kt
+++ b/src/all/mangapark/src/eu/kanade/tachiyomi/extension/all/mangapark/MangaPark.kt
@@ -1,289 +1,251 @@
package eu.kanade.tachiyomi.extension.all.mangapark
-import eu.kanade.tachiyomi.lib.cryptoaes.CryptoAES
-import eu.kanade.tachiyomi.lib.cryptoaes.Deobfuscator
+import android.app.Application
+import android.widget.Toast
+import androidx.preference.ListPreference
+import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.network.GET
-import eu.kanade.tachiyomi.network.asObservableSuccess
+import eu.kanade.tachiyomi.network.POST
+import eu.kanade.tachiyomi.network.interceptor.rateLimitHost
+import eu.kanade.tachiyomi.source.ConfigurableSource
+import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
-import eu.kanade.tachiyomi.source.online.ParsedHttpSource
+import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.asJsoup
+import kotlinx.serialization.decodeFromString
+import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
-import kotlinx.serialization.json.jsonArray
-import kotlinx.serialization.json.jsonPrimitive
import okhttp3.HttpUrl.Companion.toHttpUrl
-import okhttp3.OkHttpClient
+import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.Request
+import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
-import org.jsoup.nodes.Document
-import org.jsoup.nodes.Element
-import rx.Observable
+import uy.kohesive.injekt.Injekt
+import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
-import java.util.Calendar
-import java.util.Locale
-import java.util.concurrent.TimeUnit
-open class MangaPark(
- final override val lang: String,
- private val siteLang: String,
-) : ParsedHttpSource() {
+class MangaPark(
+ override val lang: String,
+ private val siteLang: String = lang,
+) : HttpSource(), ConfigurableSource {
- override val name: String = "MangaPark v3"
-
- override val baseUrl: String = "https://mangapark.net"
+ override val name = "MangaPark"
override val supportsLatest = true
- override val id: Long = when (lang) {
- "zh-Hans" -> 6306867705763005424
- "zh-Hant" -> 4563855043528673539
- else -> super.id
- }
+ override val versionId = 2
+
+ private val preference =
+ Injekt.get().getSharedPreferences("source_$id", 0x0000)
+
+ private val domain =
+ preference.getString(MIRROR_PREF_KEY, MIRROR_PREF_DEFAULT) ?: MIRROR_PREF_DEFAULT
+
+ override val baseUrl = "https://$domain"
+
+ private val apiUrl = "$baseUrl/apo/"
private val json: Json by injectLazy()
- private val mpFilters = MangaParkFilters()
-
- override val client: OkHttpClient = network.cloudflareClient.newBuilder()
- .connectTimeout(10, TimeUnit.SECONDS)
- .readTimeout(30, TimeUnit.SECONDS)
+ override val client = network.cloudflareClient.newBuilder()
+ .addInterceptor(CookieInterceptor(domain, "nsfw", "2"))
+ .rateLimitHost(apiUrl.toHttpUrl(), 1)
.build()
- // Site Browse Helper
- private fun browseMangaSelector(): String = "div#subject-list div.col"
+ override fun headersBuilder() = super.headersBuilder()
+ .set("Referer", "$baseUrl/")
- private fun browseNextPageSelector(): String =
- "div#mainer nav.d-none .pagination .page-item:last-of-type:not(.disabled)"
+ override fun popularMangaRequest(page: Int) = searchMangaRequest(page, "", SortFilter.POPULAR)
+ override fun popularMangaParse(response: Response) = searchMangaParse(response)
- private fun browseMangaFromElement(element: Element): SManga {
- return SManga.create().apply {
- setUrlWithoutDomain(element.select("a.fw-bold").attr("href"))
- title = element.select("a.fw-bold").text()
- thumbnail_url = element.select("a.position-relative img").attr("abs:src")
- }
- }
+ override fun latestUpdatesRequest(page: Int) = searchMangaRequest(page, "", SortFilter.LATEST)
+ override fun latestUpdatesParse(response: Response) = searchMangaParse(response)
- // Latest
- override fun latestUpdatesRequest(page: Int): Request =
- GET("$baseUrl/browse?sort=update&page=$page")
+ override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
+ val payload = GraphQL(
+ SearchVariables(
+ SearchPayload(
+ page = page,
+ size = size,
+ query = query.takeUnless(String::isEmpty),
+ incGenres = filters.firstInstanceOrNull()?.included,
+ excGenres = filters.firstInstanceOrNull()?.excluded,
+ incTLangs = listOf(siteLang),
+ incOLangs = filters.firstInstanceOrNull()?.checked,
+ sortby = filters.firstInstanceOrNull()?.selected,
+ chapCount = filters.firstInstanceOrNull()?.selected,
+ origStatus = filters.firstInstanceOrNull()?.selected,
+ siteStatus = filters.firstInstanceOrNull()?.selected,
+ ),
+ ),
+ SEARCH_QUERY,
+ ).toJsonRequestBody()
- override fun latestUpdatesSelector(): String = browseMangaSelector()
-
- override fun latestUpdatesNextPageSelector(): String = browseNextPageSelector()
-
- override fun latestUpdatesFromElement(element: Element): SManga =
- browseMangaFromElement(element)
-
- // Popular
- override fun popularMangaRequest(page: Int): Request =
- GET("$baseUrl/browse?sort=d007&page=$page")
-
- override fun popularMangaSelector(): String = browseMangaSelector()
-
- override fun popularMangaNextPageSelector(): String = browseNextPageSelector()
-
- override fun popularMangaFromElement(element: Element): SManga =
- browseMangaFromElement(element)
-
- // Search
- override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable {
- return when {
- query.startsWith(PREFIX_ID_SEARCH) -> fetchSearchIdManga(query)
- query.isNotBlank() -> fetchSearchManga(page, query)
- else -> fetchGenreSearchManga(page, filters)
- }
- }
-
- // Search With Manga ID
- private fun fetchSearchIdManga(idWithPrefix: String): Observable {
- val id = idWithPrefix.removePrefix(PREFIX_ID_SEARCH)
- return client.newCall(GET("$baseUrl/comic/$id", headers))
- .asObservableSuccess()
- .map { response ->
- MangasPage(listOf(mangaDetailsParse(response.asJsoup())), false)
- }
- }
-
- // Search WIth Query
- private fun fetchSearchManga(page: Int, query: String): Observable {
- return client.newCall(GET("$baseUrl/search?word=$query&page=$page", headers))
- .asObservableSuccess()
- .map { response ->
- searchMangaParse(response)
- }
- }
-
- // Search With Filter
- private fun fetchGenreSearchManga(page: Int, filters: FilterList): Observable {
- val url = "$baseUrl/browse".toHttpUrl().newBuilder()
- .addQueryParameter("page", page.toString()).let { mpFilters.addFiltersToUrl(it, filters) }
-
- return client.newCall(GET(url, headers))
- .asObservableSuccess()
- .map { response ->
- searchMangaParse(response)
- }
- }
-
- override fun searchMangaSelector(): String = "div#search-list div.col"
-
- override fun searchMangaNextPageSelector(): String =
- "div#mainer nav.d-none .pagination .page-item:last-of-type:not(.disabled)"
-
- override fun searchMangaFromElement(element: Element): SManga {
- return SManga.create().apply {
- setUrlWithoutDomain(element.select("a.fw-bold").attr("href"))
- title = element.select("a.fw-bold").text()
- thumbnail_url = element.select("a.position-relative img").attr("abs:src")
- }
+ return POST(apiUrl, headers, payload)
}
override fun searchMangaParse(response: Response): MangasPage {
- val document = response.asJsoup()
- val isBrowse = response.request.url.pathSegments[0] == "browse"
- val mangaSelector = if (isBrowse) browseMangaSelector() else searchMangaSelector()
- val nextPageSelector = if (isBrowse) browseNextPageSelector() else searchMangaNextPageSelector()
+ runCatching(::getGenres)
- val mangas = document.select(mangaSelector).map { element ->
- if (isBrowse) browseMangaFromElement(element) else searchMangaFromElement(element)
- }
+ val result = response.parseAs()
- val hasNextPage = document.select(nextPageSelector).first() != null
+ val entries = result.data.searchComics.items.map { it.data.toSManga() }
+ val hasNextPage = entries.size == size
- return MangasPage(mangas, hasNextPage)
+ return MangasPage(entries, hasNextPage)
}
- // Manga Details
- override fun mangaDetailsParse(document: Document): SManga {
- val infoElement = document.select("div#mainer div.container-fluid")
+ private var genreCache: List> = emptyList()
+ private var genreFetchAttempt = 0
- return SManga.create().apply {
- setUrlWithoutDomain(infoElement.select("h3.item-title a").attr("href"))
+ private fun getGenres() {
+ if (genreCache.isEmpty() && genreFetchAttempt < 3) {
+ val elements = runCatching {
+ client.newCall(GET("$baseUrl/search", headers)).execute()
+ .use { it.asJsoup() }
+ .select("div.flex-col:contains(Genres) div.whitespace-nowrap")
+ }.getOrNull().orEmpty()
- title = infoElement.select("h3.item-title").text()
+ genreCache = elements.mapNotNull {
+ val name = it.selectFirst("span.whitespace-nowrap")
+ ?.text()?.takeUnless(String::isEmpty)
+ ?: return@mapNotNull null
- description = infoElement.select("div.limit-height-body")
- .select("h5.text-muted, div.limit-html")
- .joinToString("\n\n") { it.text().trim() } + "\n\nAlt. Titles" + infoElement
- .select("div.alias-set").text()
- .split("/").joinToString(", ") { it.trim() }
+ val key = it.attr("q:key")
+ .takeUnless(String::isEmpty) ?: return@mapNotNull null
- author = infoElement.select("div.attr-item:contains(author) a")
- .joinToString { it.text().trim() }
-
- status = infoElement.select("div.attr-item:contains(status) span")
- .text().parseStatus()
-
- thumbnail_url = infoElement.select("div.detail-set div.attr-cover img").attr("abs:src")
-
- genre = infoElement.select("div.attr-item:contains(genres) span span")
- .joinToString { it.text().trim() }
+ Pair(name, key)
+ }
+ genreFetchAttempt++
}
}
- private fun String?.parseStatus() = if (this == null) {
- SManga.UNKNOWN
- } else {
- when {
- this.lowercase(Locale.US).contains("ongoing") -> SManga.ONGOING
- this.lowercase(Locale.US).contains("hiatus") -> SManga.ONGOING
- this.lowercase(Locale.US).contains("completed") -> SManga.COMPLETED
- else -> SManga.UNKNOWN
+ override fun getFilterList(): FilterList {
+ val filters = mutableListOf>(
+ SortFilter(),
+ OriginalLanguageFilter(),
+ OriginalStatusFilter(),
+ UploadStatusFilter(),
+ ChapterCountFilter(),
+ )
+
+ if (genreCache.isEmpty()) {
+ filters += listOf(
+ Filter.Separator(),
+ Filter.Header("Press 'reset' to attempt to load genres"),
+ )
+ } else {
+ filters.addAll(1, listOf(GenreFilter(genreCache)))
}
+
+ return FilterList(filters)
+ }
+
+ override fun mangaDetailsRequest(manga: SManga): Request {
+ val payload = GraphQL(
+ IdVariables(manga.url.substringAfterLast("#")),
+ DETAILS_QUERY,
+ ).toJsonRequestBody()
+
+ return POST(apiUrl, headers, payload)
+ }
+
+ override fun mangaDetailsParse(response: Response): SManga {
+ val result = response.parseAs()
+
+ return result.data.comic.data.toSManga()
+ }
+
+ override fun getMangaUrl(manga: SManga) = baseUrl + manga.url.substringBeforeLast("#")
+
+ override fun chapterListRequest(manga: SManga): Request {
+ val payload = GraphQL(
+ IdVariables(manga.url.substringAfterLast("#")),
+ CHAPTERS_QUERY,
+ ).toJsonRequestBody()
+
+ return POST(apiUrl, headers, payload)
}
override fun chapterListParse(response: Response): List {
- val chapterListHtml = response.asJsoup().select("div.episode-list #chap-index")
- return chapterListHtml.flatMap { it.select(chapterListSelector()).map { chapElem -> chapterFromElement(chapElem) } }
+ val result = response.parseAs()
+
+ return result.data.chapterList.map { it.data.toSChapter() }.reversed()
}
- override fun chapterListSelector(): String {
- return when (lang) {
- "en" -> "div.p-2:not(:has(.px-3))"
- // To handle both "/comic/1/test/c0-en" and "/comic/1/test/c0-en/" like url
- else -> "div.p-2:has(.px-3 a[href\$=\"$siteLang\"]), div.p-2:has(.px-3 a[href\$=\"$siteLang/\"])"
+ override fun getChapterUrl(chapter: SChapter) = baseUrl + chapter.url.substringBeforeLast("#")
+
+ override fun pageListRequest(chapter: SChapter): Request {
+ val payload = GraphQL(
+ IdVariables(chapter.url.substringAfterLast("#")),
+ PAGES_QUERY,
+ ).toJsonRequestBody()
+
+ return POST(apiUrl, headers, payload)
+ }
+
+ override fun pageListParse(response: Response): List {
+ val result = response.parseAs()
+
+ return result.data.chapterPages.data.imageFile.urlList.mapIndexed { idx, url ->
+ Page(idx, "", url)
}
}
- override fun chapterFromElement(element: Element): SChapter {
- val urlElement = element.select("a.ms-3")
+ override fun setupPreferenceScreen(screen: PreferenceScreen) {
+ ListPreference(screen.context).apply {
+ key = MIRROR_PREF_KEY
+ title = "Preferred Mirror"
+ entries = mirrors
+ entryValues = mirrors
+ setDefaultValue(MIRROR_PREF_DEFAULT)
+ summary = "%s"
- return SChapter.create().apply {
- name = urlElement.text().removePrefix("Ch").trim()
- date_upload = element.select("i.text-nowrap").text().parseChapterDate()
- setUrlWithoutDomain(urlElement.attr("href").removeSuffix("/"))
- }
- }
-
- private fun String?.parseChapterDate(): Long {
- if (this == null) return 0L
- val value = this.split(' ')[0].toInt()
-
- return when (this.split(' ')[1].removeSuffix("s")) {
- "sec" -> Calendar.getInstance().apply {
- add(Calendar.SECOND, value * -1)
- }.timeInMillis
- "min" -> Calendar.getInstance().apply {
- add(Calendar.MINUTE, value * -1)
- }.timeInMillis
- "hour" -> Calendar.getInstance().apply {
- add(Calendar.HOUR_OF_DAY, value * -1)
- }.timeInMillis
- "day" -> Calendar.getInstance().apply {
- add(Calendar.DATE, value * -1)
- }.timeInMillis
- "week" -> Calendar.getInstance().apply {
- add(Calendar.DATE, value * 7 * -1)
- }.timeInMillis
- "month" -> Calendar.getInstance().apply {
- add(Calendar.MONTH, value * -1)
- }.timeInMillis
- "year" -> Calendar.getInstance().apply {
- add(Calendar.YEAR, value * -1)
- }.timeInMillis
- else -> {
- return 0L
+ setOnPreferenceChangeListener { _, _ ->
+ Toast.makeText(screen.context, "Restart Tachiyomi to apply changes", Toast.LENGTH_LONG).show()
+ true
}
- }
+ }.also(screen::addPreference)
}
- override fun pageListParse(document: Document): List {
- if (document.selectFirst("div.wrapper-deleted") != null) {
- throw Exception("The chapter content seems to be deleted.\n\nContact the site owner if possible.")
- }
+ private inline fun Response.parseAs(): T =
+ use { body.string() }.let(json::decodeFromString)
- val script = document.selectFirst("script:containsData(imgHttpLis):containsData(amWord):containsData(amPass)")?.html()
- ?: throw RuntimeException("Couldn't find script with image data.")
+ private inline fun List<*>.firstInstanceOrNull(): T? =
+ filterIsInstance().firstOrNull()
- val imgHttpLisString = script.substringAfter("const imgHttpLis =").substringBefore(";").trim()
- val imgHttpLis = json.parseToJsonElement(imgHttpLisString).jsonArray.map { it.jsonPrimitive.content }
- val amWord = script.substringAfter("const amWord =").substringBefore(";").trim()
- val amPass = script.substringAfter("const amPass =").substringBefore(";").trim()
+ private inline fun T.toJsonRequestBody() =
+ json.encodeToString(this).toRequestBody(JSON_MEDIA_TYPE)
- val evaluatedPass: String = Deobfuscator.deobfuscateJsPassword(amPass)
- val imgAccListString = CryptoAES.decrypt(amWord.removeSurrounding("\""), evaluatedPass)
- val imgAccList = json.parseToJsonElement(imgAccListString).jsonArray.map { it.jsonPrimitive.content }
-
- return imgHttpLis.zip(imgAccList).mapIndexed { i, (imgUrl, imgAcc) ->
- Page(i, imageUrl = "$imgUrl?$imgAcc")
- }
+ override fun imageUrlParse(response: Response): String {
+ throw UnsupportedOperationException("Not Used")
}
- override fun getFilterList() = mpFilters.getFilterList()
-
- // Unused Stuff
-
- override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request =
- throw UnsupportedOperationException("Not used")
-
- override fun imageUrlParse(document: Document): String =
- throw UnsupportedOperationException("Not used")
-
companion object {
+ private const val size = 24
+ private val JSON_MEDIA_TYPE = "application/json; charset=utf-8".toMediaTypeOrNull()
- const val PREFIX_ID_SEARCH = "id:"
+ private const val MIRROR_PREF_KEY = "pref_mirror"
+ private const val MIRROR_PREF_DEFAULT = "mangapark.net"
+ private val mirrors = arrayOf(
+ "mangapark.net",
+ "mangapark.com",
+ "mangapark.org",
+ "mangapark.me",
+ "mangapark.io",
+ "mangapark.to",
+ "comicpark.org",
+ "comicpark.to",
+ "readpark.org",
+ "readpark.net",
+ "parkmanga.com",
+ "parkmanga.net",
+ "parkmanga.org",
+ "mpark.to",
+ )
}
}
diff --git a/src/all/mangapark/src/eu/kanade/tachiyomi/extension/all/mangapark/MangaParkDto.kt b/src/all/mangapark/src/eu/kanade/tachiyomi/extension/all/mangapark/MangaParkDto.kt
new file mode 100644
index 000000000..6293244c0
--- /dev/null
+++ b/src/all/mangapark/src/eu/kanade/tachiyomi/extension/all/mangapark/MangaParkDto.kt
@@ -0,0 +1,139 @@
+package eu.kanade.tachiyomi.extension.all.mangapark
+
+import eu.kanade.tachiyomi.source.model.SChapter
+import eu.kanade.tachiyomi.source.model.SManga
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import org.jsoup.Jsoup
+
+typealias SearchResponse = Data
+typealias DetailsResponse = Data
+typealias ChapterListResponse = Data
+typealias PageListResponse = Data
+
+@Serializable
+data class Data(val data: T)
+
+@Serializable
+data class Items(val items: List)
+
+@Serializable
+data class SearchComics(
+ @SerialName("get_searchComic") val searchComics: Items>,
+)
+
+@Serializable
+data class ComicNode(
+ @SerialName("get_comicNode") val comic: Data,
+)
+
+@Serializable
+data class MangaParkComic(
+ val id: String,
+ val name: String,
+ val altNames: List? = null,
+ val authors: List? = null,
+ val artists: List? = null,
+ val genres: List? = null,
+ val originalStatus: String? = null,
+ val uploadStatus: String? = null,
+ val summary: String? = null,
+ @SerialName("urlCoverOri") val cover: String? = null,
+ val urlPath: String,
+) {
+ fun toSManga() = SManga.create().apply {
+ url = "$urlPath#$id"
+ title = name
+ thumbnail_url = cover
+ 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)
+ }
+ }
+ }
+ genre = genres?.joinToString { it.replace("_", " ").toCamelCase() }
+ status = when (originalStatus) {
+ "ongoing" -> SManga.ONGOING
+ "completed" -> {
+ if (uploadStatus == "ongoing") {
+ SManga.PUBLISHING_FINISHED
+ } else {
+ SManga.COMPLETED
+ }
+ }
+ "hiatus" -> SManga.ON_HIATUS
+ "cancelled" -> SManga.CANCELLED
+ else -> SManga.UNKNOWN
+ }
+ initialized = true
+ }
+
+ companion object {
+ private fun String.toCamelCase(): String {
+ val result = StringBuilder(length)
+ var capitalize = true
+ for (char in this) {
+ result.append(
+ if (capitalize) {
+ char.uppercase()
+ } else {
+ char.lowercase()
+ },
+ )
+ capitalize = char.isWhitespace()
+ }
+ return result.toString()
+ }
+ }
+}
+
+@Serializable
+data class ChapterList(
+ @SerialName("get_comicChapterList") val chapterList: List>,
+)
+
+@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,
+) {
+ fun toSChapter() = SChapter.create().apply {
+ url = "$urlPath#$id"
+ name = buildString {
+ append(displayName)
+ title?.let { append(": ", it) }
+ }
+ date_upload = dateModify ?: dateCreate ?: 0L
+ }
+}
+
+@Serializable
+data class ChapterPages(
+ @SerialName("get_chapterNode") val chapterPages: Data,
+)
+
+@Serializable
+data class ImageFiles(
+ val imageFile: UrlList,
+)
+
+@Serializable
+data class UrlList(
+ val urlList: List,
+)
diff --git a/src/all/mangapark/src/eu/kanade/tachiyomi/extension/all/mangapark/MangaParkFactory.kt b/src/all/mangapark/src/eu/kanade/tachiyomi/extension/all/mangapark/MangaParkFactory.kt
index ebf77e545..500bb871a 100644
--- a/src/all/mangapark/src/eu/kanade/tachiyomi/extension/all/mangapark/MangaParkFactory.kt
+++ b/src/all/mangapark/src/eu/kanade/tachiyomi/extension/all/mangapark/MangaParkFactory.kt
@@ -1,112 +1,107 @@
package eu.kanade.tachiyomi.extension.all.mangapark
-import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceFactory
class MangaParkFactory : SourceFactory {
- override fun createSources(): List