MangaPark v5 (#19341)

* MangaPark: cleanup

fresh start

* basic functionality

* webview urls

* filters

* review changes

* description logic & id in url & a filter

* bump versionId

to differentiate from old v2 extension which was removed

* update icons

* Domain preference
This commit is contained in:
AwkwardPeak7 2023-12-21 17:52:58 +05:00 committed by GitHub
parent 31f80579cb
commit 357589e912
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 733 additions and 703 deletions

View File

@ -1,22 +1,2 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest />
<application>
<activity
android:name=".all.mangapark.MangaParkUrlActivity"
android:excludeFromRecents="true"
android:exported="true"
android:theme="@android:style/Theme.NoDisplay">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="mangapark.net"
android:pathPattern="/comic/..*"
android:scheme="https" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -3,15 +3,11 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization' apply plugin: 'kotlinx-serialization'
ext { ext {
extName = 'MangaPark v3' extName = 'MangaPark'
pkgNameSuffix = 'all.mangapark' pkgNameSuffix = 'all.mangapark'
extClass = '.MangaParkFactory' extClass = '.MangaParkFactory'
extVersionCode = 18 extVersionCode = 19
isNsfw = true isNsfw = true
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(':lib-cryptoaes'))
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View File

@ -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)
}
}
}

View File

@ -1,289 +1,251 @@
package eu.kanade.tachiyomi.extension.all.mangapark package eu.kanade.tachiyomi.extension.all.mangapark
import eu.kanade.tachiyomi.lib.cryptoaes.CryptoAES import android.app.Application
import eu.kanade.tachiyomi.lib.cryptoaes.Deobfuscator import android.widget.Toast
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.network.GET 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.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter 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.ParsedHttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document import uy.kohesive.injekt.Injekt
import org.jsoup.nodes.Element import uy.kohesive.injekt.api.get
import rx.Observable
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.util.Calendar
import java.util.Locale
import java.util.concurrent.TimeUnit
open class MangaPark( class MangaPark(
final override val lang: String, override val lang: String,
private val siteLang: String, private val siteLang: String = lang,
) : ParsedHttpSource() { ) : HttpSource(), ConfigurableSource {
override val name: String = "MangaPark v3" override val name = "MangaPark"
override val baseUrl: String = "https://mangapark.net"
override val supportsLatest = true override val supportsLatest = true
override val id: Long = when (lang) { override val versionId = 2
"zh-Hans" -> 6306867705763005424
"zh-Hant" -> 4563855043528673539 private val preference =
else -> super.id Injekt.get<Application>().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 json: Json by injectLazy()
private val mpFilters = MangaParkFilters() override val client = network.cloudflareClient.newBuilder()
.addInterceptor(CookieInterceptor(domain, "nsfw", "2"))
override val client: OkHttpClient = network.cloudflareClient.newBuilder() .rateLimitHost(apiUrl.toHttpUrl(), 1)
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build() .build()
// Site Browse Helper override fun headersBuilder() = super.headersBuilder()
private fun browseMangaSelector(): String = "div#subject-list div.col" .set("Referer", "$baseUrl/")
private fun browseNextPageSelector(): String = override fun popularMangaRequest(page: Int) = searchMangaRequest(page, "", SortFilter.POPULAR)
"div#mainer nav.d-none .pagination .page-item:last-of-type:not(.disabled)" override fun popularMangaParse(response: Response) = searchMangaParse(response)
private fun browseMangaFromElement(element: Element): SManga { override fun latestUpdatesRequest(page: Int) = searchMangaRequest(page, "", SortFilter.LATEST)
return SManga.create().apply { override fun latestUpdatesParse(response: Response) = searchMangaParse(response)
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")
}
}
// Latest override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
override fun latestUpdatesRequest(page: Int): Request = val payload = GraphQL(
GET("$baseUrl/browse?sort=update&page=$page") SearchVariables(
SearchPayload(
page = page,
size = size,
query = query.takeUnless(String::isEmpty),
incGenres = filters.firstInstanceOrNull<GenreFilter>()?.included,
excGenres = filters.firstInstanceOrNull<GenreFilter>()?.excluded,
incTLangs = listOf(siteLang),
incOLangs = filters.firstInstanceOrNull<OriginalLanguageFilter>()?.checked,
sortby = filters.firstInstanceOrNull<SortFilter>()?.selected,
chapCount = filters.firstInstanceOrNull<ChapterCountFilter>()?.selected,
origStatus = filters.firstInstanceOrNull<OriginalStatusFilter>()?.selected,
siteStatus = filters.firstInstanceOrNull<UploadStatusFilter>()?.selected,
),
),
SEARCH_QUERY,
).toJsonRequestBody()
override fun latestUpdatesSelector(): String = browseMangaSelector() return POST(apiUrl, headers, payload)
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<MangasPage> {
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<MangasPage> {
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<MangasPage> {
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<MangasPage> {
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")
}
} }
override fun searchMangaParse(response: Response): MangasPage { override fun searchMangaParse(response: Response): MangasPage {
val document = response.asJsoup() runCatching(::getGenres)
val isBrowse = response.request.url.pathSegments[0] == "browse"
val mangaSelector = if (isBrowse) browseMangaSelector() else searchMangaSelector()
val nextPageSelector = if (isBrowse) browseNextPageSelector() else searchMangaNextPageSelector()
val mangas = document.select(mangaSelector).map { element -> val result = response.parseAs<SearchResponse>()
if (isBrowse) browseMangaFromElement(element) else searchMangaFromElement(element)
val entries = result.data.searchComics.items.map { it.data.toSManga() }
val hasNextPage = entries.size == size
return MangasPage(entries, hasNextPage)
} }
val hasNextPage = document.select(nextPageSelector).first() != null private var genreCache: List<Pair<String, String>> = emptyList()
private var genreFetchAttempt = 0
return MangasPage(mangas, hasNextPage) 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()
genreCache = elements.mapNotNull {
val name = it.selectFirst("span.whitespace-nowrap")
?.text()?.takeUnless(String::isEmpty)
?: return@mapNotNull null
val key = it.attr("q:key")
.takeUnless(String::isEmpty) ?: return@mapNotNull null
Pair(name, key)
} }
genreFetchAttempt++
// Manga Details
override fun mangaDetailsParse(document: Document): SManga {
val infoElement = document.select("div#mainer div.container-fluid")
return SManga.create().apply {
setUrlWithoutDomain(infoElement.select("h3.item-title a").attr("href"))
title = infoElement.select("h3.item-title").text()
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() }
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() }
} }
} }
private fun String?.parseStatus() = if (this == null) { override fun getFilterList(): FilterList {
SManga.UNKNOWN val filters = mutableListOf<Filter<*>>(
SortFilter(),
OriginalLanguageFilter(),
OriginalStatusFilter(),
UploadStatusFilter(),
ChapterCountFilter(),
)
if (genreCache.isEmpty()) {
filters += listOf(
Filter.Separator(),
Filter.Header("Press 'reset' to attempt to load genres"),
)
} else { } else {
when { filters.addAll(1, listOf(GenreFilter(genreCache)))
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
} }
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<DetailsResponse>()
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<SChapter> { override fun chapterListParse(response: Response): List<SChapter> {
val chapterListHtml = response.asJsoup().select("div.episode-list #chap-index") val result = response.parseAs<ChapterListResponse>()
return chapterListHtml.flatMap { it.select(chapterListSelector()).map { chapElem -> chapterFromElement(chapElem) } }
return result.data.chapterList.map { it.data.toSChapter() }.reversed()
} }
override fun chapterListSelector(): String { override fun getChapterUrl(chapter: SChapter) = baseUrl + chapter.url.substringBeforeLast("#")
return when (lang) {
"en" -> "div.p-2:not(:has(.px-3))" override fun pageListRequest(chapter: SChapter): Request {
// To handle both "/comic/1/test/c0-en" and "/comic/1/test/c0-en/" like url val payload = GraphQL(
else -> "div.p-2:has(.px-3 a[href\$=\"$siteLang\"]), div.p-2:has(.px-3 a[href\$=\"$siteLang/\"])" IdVariables(chapter.url.substringAfterLast("#")),
PAGES_QUERY,
).toJsonRequestBody()
return POST(apiUrl, headers, payload)
}
override fun pageListParse(response: Response): List<Page> {
val result = response.parseAs<PageListResponse>()
return result.data.chapterPages.data.imageFile.urlList.mapIndexed { idx, url ->
Page(idx, "", url)
} }
} }
override fun chapterFromElement(element: Element): SChapter { override fun setupPreferenceScreen(screen: PreferenceScreen) {
val urlElement = element.select("a.ms-3") 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 { setOnPreferenceChangeListener { _, _ ->
name = urlElement.text().removePrefix("Ch").trim() Toast.makeText(screen.context, "Restart Tachiyomi to apply changes", Toast.LENGTH_LONG).show()
date_upload = element.select("i.text-nowrap").text().parseChapterDate() true
setUrlWithoutDomain(urlElement.attr("href").removeSuffix("/"))
} }
}.also(screen::addPreference)
} }
private fun String?.parseChapterDate(): Long { private inline fun <reified T> Response.parseAs(): T =
if (this == null) return 0L use { body.string() }.let(json::decodeFromString)
val value = this.split(' ')[0].toInt()
return when (this.split(' ')[1].removeSuffix("s")) { private inline fun <reified T> List<*>.firstInstanceOrNull(): T? =
"sec" -> Calendar.getInstance().apply { filterIsInstance<T>().firstOrNull()
add(Calendar.SECOND, value * -1)
}.timeInMillis private inline fun <reified T : Any> T.toJsonRequestBody() =
"min" -> Calendar.getInstance().apply { json.encodeToString(this).toRequestBody(JSON_MEDIA_TYPE)
add(Calendar.MINUTE, value * -1)
}.timeInMillis override fun imageUrlParse(response: Response): String {
"hour" -> Calendar.getInstance().apply { throw UnsupportedOperationException("Not Used")
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
} }
}
}
override fun pageListParse(document: Document): List<Page> {
if (document.selectFirst("div.wrapper-deleted") != null) {
throw Exception("The chapter content seems to be deleted.\n\nContact the site owner if possible.")
}
val script = document.selectFirst("script:containsData(imgHttpLis):containsData(amWord):containsData(amPass)")?.html()
?: throw RuntimeException("Couldn't find script with image data.")
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()
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 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 { 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",
)
} }
} }

View File

@ -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<SearchComics>
typealias DetailsResponse = Data<ComicNode>
typealias ChapterListResponse = Data<ChapterList>
typealias PageListResponse = Data<ChapterPages>
@Serializable
data class Data<T>(val data: T)
@Serializable
data class Items<T>(val items: List<T>)
@Serializable
data class SearchComics(
@SerialName("get_searchComic") val searchComics: Items<Data<MangaParkComic>>,
)
@Serializable
data 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,
) {
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<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,
) {
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<ImageFiles>,
)
@Serializable
data class ImageFiles(
val imageFile: UrlList,
)
@Serializable
data class UrlList(
val urlList: List<String>,
)

View File

@ -1,112 +1,107 @@
package eu.kanade.tachiyomi.extension.all.mangapark package eu.kanade.tachiyomi.extension.all.mangapark
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceFactory import eu.kanade.tachiyomi.source.SourceFactory
class MangaParkFactory : SourceFactory { class MangaParkFactory : SourceFactory {
override fun createSources(): List<Source> = languages.map { MangaPark(it.lang, it.siteLang) } override fun createSources() = listOf(
MangaPark("af"),
MangaPark("sq"),
MangaPark("am"),
MangaPark("ar"),
MangaPark("hy"),
MangaPark("az"),
MangaPark("be"),
MangaPark("bn"),
MangaPark("bs"),
MangaPark("bg"),
MangaPark("my"),
MangaPark("km"),
MangaPark("ca"),
MangaPark("ceb"),
MangaPark("zh"),
MangaPark("zh-Hans", "zh_hk"),
MangaPark("zh-Hant", "zh_tw"),
MangaPark("hr"),
MangaPark("cs"),
MangaPark("da"),
MangaPark("nl"),
MangaPark("en"),
MangaPark("eo"),
MangaPark("et"),
MangaPark("fo"),
MangaPark("fil"),
MangaPark("fi"),
MangaPark("fr"),
MangaPark("ka"),
MangaPark("de"),
MangaPark("el"),
MangaPark("gn"),
MangaPark("ht"),
MangaPark("ha"),
MangaPark("he"),
MangaPark("hi"),
MangaPark("hu"),
MangaPark("is"),
MangaPark("ig"),
MangaPark("id"),
MangaPark("ga"),
MangaPark("it"),
MangaPark("ja"),
MangaPark("jv"),
MangaPark("kk"),
MangaPark("ko"),
MangaPark("ku"),
MangaPark("ky"),
MangaPark("lo"),
MangaPark("lv"),
MangaPark("lt"),
MangaPark("lb"),
MangaPark("mk"),
MangaPark("mg"),
MangaPark("ms"),
MangaPark("ml"),
MangaPark("mt"),
MangaPark("mi"),
MangaPark("mo"),
MangaPark("mn"),
MangaPark("ne"),
MangaPark("no"),
MangaPark("ny"),
MangaPark("ps"),
MangaPark("fa"),
MangaPark("pl"),
MangaPark("pt"),
MangaPark("pt-BR", "pt_br"),
MangaPark("ro"),
MangaPark("rm"),
MangaPark("ru"),
MangaPark("sm"),
MangaPark("sr"),
MangaPark("sh"),
MangaPark("st"),
MangaPark("sn"),
MangaPark("sd"),
MangaPark("si"),
MangaPark("sk"),
MangaPark("sl"),
MangaPark("so"),
MangaPark("es"),
MangaPark("es-419", "es_419"),
MangaPark("sw"),
MangaPark("sv"),
MangaPark("tg"),
MangaPark("ta"),
MangaPark("th"),
MangaPark("ti"),
MangaPark("to"),
MangaPark("tr"),
MangaPark("tk"),
MangaPark("uk"),
MangaPark("ur"),
MangaPark("uz"),
MangaPark("vi"),
MangaPark("yo"),
MangaPark("zu"),
MangaPark("other", "_t"),
)
} }
class LanguageOption(val lang: String, val siteLang: String = lang)
private val languages = listOf(
// LanguageOption("<Language Format>","<Language Format used in site.>"),
LanguageOption("af"),
LanguageOption("sq"),
LanguageOption("am"),
LanguageOption("ar"),
LanguageOption("hy"),
LanguageOption("az"),
LanguageOption("be"),
LanguageOption("bn"),
LanguageOption("bs"),
LanguageOption("bg"),
LanguageOption("my"),
LanguageOption("km"),
LanguageOption("ca"),
LanguageOption("ceb"),
LanguageOption("zh"),
LanguageOption("zh-Hans", "zh_hk"),
LanguageOption("zh-Hant", "zh_tw"),
LanguageOption("hr"),
LanguageOption("cs"),
LanguageOption("da"),
LanguageOption("nl"),
LanguageOption("en"),
LanguageOption("eo"),
LanguageOption("et"),
LanguageOption("fo"),
LanguageOption("fil"),
LanguageOption("fi"),
LanguageOption("fr"),
LanguageOption("ka"),
LanguageOption("de"),
LanguageOption("el"),
LanguageOption("gn"),
LanguageOption("ht"),
LanguageOption("ha"),
LanguageOption("he"),
LanguageOption("hi"),
LanguageOption("hu"),
LanguageOption("is"),
LanguageOption("ig"),
LanguageOption("id"),
LanguageOption("ga"),
LanguageOption("it"),
LanguageOption("ja"),
LanguageOption("jv"),
LanguageOption("kk"),
LanguageOption("ko"),
LanguageOption("ku"),
LanguageOption("ky"),
LanguageOption("lo"),
LanguageOption("lv"),
LanguageOption("lt"),
LanguageOption("lb"),
LanguageOption("mk"),
LanguageOption("mg"),
LanguageOption("ms"),
LanguageOption("ml"),
LanguageOption("mt"),
LanguageOption("mi"),
LanguageOption("mo"),
LanguageOption("mn"),
LanguageOption("ne"),
LanguageOption("no"),
LanguageOption("ny"),
LanguageOption("ps"),
LanguageOption("fa"),
LanguageOption("pl"),
LanguageOption("pt"),
LanguageOption("pt-BR", "pt_br"),
LanguageOption("ro"),
LanguageOption("rm"),
LanguageOption("ru"),
LanguageOption("sm"),
LanguageOption("sr"),
LanguageOption("sh"),
LanguageOption("st"),
LanguageOption("sn"),
LanguageOption("sd"),
LanguageOption("si"),
LanguageOption("sk"),
LanguageOption("sl"),
LanguageOption("so"),
LanguageOption("es"),
LanguageOption("es-419", "es_419"),
LanguageOption("sw"),
LanguageOption("sv"),
LanguageOption("tg"),
LanguageOption("ta"),
LanguageOption("th"),
LanguageOption("ti"),
LanguageOption("to"),
LanguageOption("tr"),
LanguageOption("tk"),
LanguageOption("uk"),
LanguageOption("ur"),
LanguageOption("uz"),
LanguageOption("vi"),
LanguageOption("yo"),
LanguageOption("zu"),
LanguageOption("other", "_t"),
)

View File

@ -2,300 +2,132 @@ package eu.kanade.tachiyomi.extension.all.mangapark
import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import okhttp3.HttpUrl
class MangaParkFilters { abstract class SelectFilter(
internal fun getFilterList(): FilterList {
return FilterList(
Filter.Header("NOTE: Ignored if using text search!"),
Filter.Separator(),
SortFilter("Sort By", defaultSort, sortList),
Filter.Separator(),
MinChapterFilter(),
MaxChapterFilter(),
Filter.Separator(),
PublicationFilter("Status", publicationList, 0),
TypeFilter("Type", typeList),
DemographicFilter("Demographic", demographicList),
ContentFilter("Content", contentList),
GenreFilter("Genre", genreList),
)
}
internal fun addFiltersToUrl(url: HttpUrl.Builder, filters: FilterList): String {
var sort = "rating.za"
var minChap: Int? = null
var maxChap: Int? = null
var publication: String? = null
val includedGenre = mutableListOf<String>()
val excludedGenre = mutableListOf<String>()
filters.forEach { filter ->
when (filter) {
is SortFilter -> {
val sortType = sortList[filter.state!!.index].value
val sortDirection = if (filter.state!!.ascending) "az" else "za"
sort = "$sortType.$sortDirection"
}
is MinChapterFilter -> {
try {
minChap = filter.state.toInt()
} catch (_: NumberFormatException) {
// Do Nothing
}
}
is MaxChapterFilter -> {
try {
maxChap = filter.state.toInt()
} catch (_: NumberFormatException) {
// Do Nothing
}
}
is PublicationFilter -> {
if (filter.state != 0) {
publication = publicationList[filter.state].value
}
}
is TypeFilter -> {
includedGenre += filter.state.filter { it.isIncluded() }.map { it.value }
excludedGenre += filter.state.filter { it.isExcluded() }.map { it.value }
}
is DemographicFilter -> {
includedGenre += filter.state.filter { it.isIncluded() }.map { it.value }
excludedGenre += filter.state.filter { it.isExcluded() }.map { it.value }
}
is ContentFilter -> {
includedGenre += filter.state.filter { it.isIncluded() }.map { it.value }
excludedGenre += filter.state.filter { it.isExcluded() }.map { it.value }
}
is GenreFilter -> {
includedGenre += filter.state.filter { it.isIncluded() }.map { it.value }
excludedGenre += filter.state.filter { it.isExcluded() }.map { it.value }
}
else -> {}
}
}
return url.apply {
if (sort != "rating.za") {
addQueryParameter(
"sort",
sort,
)
}
if (includedGenre.isNotEmpty() || excludedGenre.isNotEmpty()) {
addQueryParameter(
"genres",
includedGenre.joinToString(",") + "|" + excludedGenre.joinToString(","),
)
}
if (publication != null) {
addQueryParameter(
"release",
publication,
)
}
addQueryParameter(
"chapters",
minMaxToChapter(minChap, maxChap),
)
}.toString()
}
private fun minMaxToChapter(minChap: Int?, maxChap: Int?): String? {
if (minChap == null && maxChap == null) return null
return when {
minChap != null && maxChap == null -> minChap
minChap == null && maxChap != null -> "0-$maxChap"
else -> "$minChap-$maxChap"
}.toString()
}
// Sort Filter
class SortItem(val name: String, val value: String)
private val sortList: List<SortItem> = listOf(
SortItem("Rating", "rating"),
SortItem("Comments", "comments"),
SortItem("Discuss", "discuss"),
SortItem("Update", "update"),
SortItem("Create", "create"),
SortItem("Name", "name"),
SortItem("Total Views", "d000"),
SortItem("Most Views 360 days", "d360"),
SortItem("Most Views 180 days", "d180"),
SortItem("Most Views 90 days", "d090"),
SortItem("Most Views 30 days", "d030"),
SortItem("Most Views 7 days", "d007"),
SortItem("Most Views 24 hours", "h024"),
SortItem("Most Views 12 hours", "h012"),
SortItem("Most Views 6 hours", "h006"),
SortItem("Most Views 60 minutes", "h001"),
)
class SortDefault(val defaultSortIndex: Int, val ascending: Boolean)
private val defaultSort: SortDefault = SortDefault(0, false)
class SortFilter(name: String, default: SortDefault, sorts: List<SortItem>) :
Filter.Sort(
name,
sorts.map { it.name }.toTypedArray(),
Selection(default.defaultSortIndex, default.ascending),
)
// Min - Max Chapter Filter
abstract class TextFilter(name: String) : Filter.Text(name)
class MinChapterFilter : TextFilter("Min. Chapters")
class MaxChapterFilter : TextFilter("Max. Chapters")
// Publication Filter
class PublicationItem(val name: String, val value: String)
private val publicationList: List<PublicationItem> = listOf(
PublicationItem("All", ""),
PublicationItem("Pending", "pending"),
PublicationItem("Ongoing", "ongoing"),
PublicationItem("Completed", "completed"),
PublicationItem("Hiatus", "hiatus"),
PublicationItem("Cancelled", "cancelled"),
)
class PublicationFilter(
name: String, name: String,
statusList: List<PublicationItem>, private val options: List<Pair<String, String>>,
defaultStatusIndex: Int, defaultValue: String? = null,
) : ) : Filter.Select<String>(
Filter.Select<String>(
name, name,
statusList.map { it.name }.toTypedArray(), options.map { it.first }.toTypedArray(),
defaultStatusIndex, options.indexOfFirst { it.second == defaultValue }.takeIf { it != -1 } ?: 0,
) ) {
val selected get() = options[state].second.takeUnless { it.isEmpty() }
// Type }
class TypeItem(name: String, val value: String) : Filter.TriState(name)
class CheckBoxFilter(name: String, val value: String) : Filter.CheckBox(name)
private val typeList: List<TypeItem> = listOf(
TypeItem("Cartoon", "cartoon"), abstract class CheckBoxGroup(
TypeItem("Comic", "comic"), name: String,
TypeItem("Doujinshi", "doujinshi"), options: List<Pair<String, String>>,
TypeItem("Manga", "manga"), ) : Filter.Group<CheckBoxFilter>(
TypeItem("Manhua", "manhua"), name,
TypeItem("Manhwa", "manhwa"), options.map { CheckBoxFilter(it.first, it.second) },
TypeItem("Webtoon", "webtoon"), ) {
) val checked get() = state.filter { it.state }.map { it.value }.takeUnless { it.isEmpty() }
}
class TypeFilter(name: String, typeList: List<TypeItem>) :
Filter.Group<TypeItem>(name, typeList) class TriStateFilter(name: String, val value: String) : Filter.TriState(name)
// Demographic abstract class TriStateGroup(
class DemographicItem(name: String, val value: String) : Filter.TriState(name) name: String,
private val options: List<Pair<String, String>>,
private val demographicList: List<DemographicItem> = listOf( ) : Filter.Group<TriStateFilter>(
DemographicItem("Shounen", "shounen"), name,
DemographicItem("Shoujo", "shoujo"), options.map { TriStateFilter(it.first, it.second) },
DemographicItem("Seinen", "seinen"), ) {
DemographicItem("Josei", "josei"), val included get() = state.filter { it.isIncluded() }.map { it.value }.takeUnless { it.isEmpty() }
) val excluded get() = state.filter { it.isExcluded() }.map { it.value }.takeUnless { it.isEmpty() }
}
class DemographicFilter(name: String, demographicList: List<DemographicItem>) :
Filter.Group<DemographicItem>(name, demographicList) class SortFilter(defaultOrder: String? = null) : SelectFilter("Sort By", sort, defaultOrder) {
companion object {
// Content private val sort = listOf(
class ContentItem(name: String, val value: String) : Filter.TriState(name) Pair("Rating Score", "field_score"),
Pair("Most Follows", "field_follow"),
private val contentList: List<ContentItem> = listOf( Pair("Most Reviews", "field_review"),
ContentItem("Adult", "adult"), Pair("Most Comments", "field_comment"),
ContentItem("Ecchi", "ecchi"), Pair("Most Chapters", "field_chapter"),
ContentItem("Gore", "gore"), Pair("New Chapters", "field_update"),
ContentItem("Hentai", "hentai"), Pair("Recently Created", "field_create"),
ContentItem("Mature", "mature"), Pair("Name A-Z", "field_name"),
ContentItem("Smut", "smut"), Pair("Total Views", "views_d000"),
) Pair("Most Views 360 days", "views_d360"),
Pair("Most Views 180 days", "views_d180"),
class ContentFilter(name: String, contentList: List<ContentItem>) : Pair("Most Views 90 days", "views_d090"),
Filter.Group<ContentItem>(name, contentList) Pair("Most Views 30 days", "views_d030"),
Pair("Most Views 7 days", "views_d007"),
// Genre Pair("Most Views 24 hours", "views_h024"),
class GenreItem(name: String, val value: String) : Filter.TriState(name) Pair("Most Views 12 hours", "views_h012"),
Pair("Most Views 6 hours", "views_h006"),
private val genreList: List<GenreItem> = listOf( Pair("Most Views 60 minutes", "views_h001"),
GenreItem("Action", "action"), )
GenreItem("Adaptation", "adaptation"),
GenreItem("Adventure", "adventure"), val POPULAR = FilterList(SortFilter("field_score"))
GenreItem("Aliens", "aliens"), val LATEST = FilterList(SortFilter("field_update"))
GenreItem("Animals", "animals"), }
GenreItem("Anthology", "anthology"), }
GenreItem("Award Winning", "award_winning"), // This Is Hidden In Web
GenreItem("Cars", "cars"), class GenreFilter(genres: List<Pair<String, String>>) : TriStateGroup("Genres", genres)
GenreItem("Comedy", "comedy"),
GenreItem("Cooking", "cooking"), abstract class StatusFilter(name: String) : SelectFilter(name, status) {
GenreItem("Crime", "crime"), companion object {
GenreItem("Crossdressing", "crossdressing"), private val status = listOf(
GenreItem("Delinquents", "delinquents"), Pair("All", ""),
GenreItem("Dementia", "dementia"), Pair("Pending", "pending"),
GenreItem("Demons", "demons"), Pair("Ongoing", "ongoing"),
GenreItem("Drama", "drama"), Pair("Completed", "completed"),
GenreItem("Fantasy", "fantasy"), Pair("Hiatus", "hiatus"),
GenreItem("Full Color", "full_color"), Pair("Cancelled", "cancelled"),
GenreItem("Game", "game"), )
GenreItem("Gender Bender", "gender_bender"), }
GenreItem("Genderswap", "genderswap"), }
GenreItem("Gyaru", "gyaru"),
GenreItem("Harem", "harem"), class OriginalLanguageFilter : CheckBoxGroup("Original Work Language", language) {
GenreItem("Historical", "historical"), companion object {
GenreItem("Horror", "horror"), private val language = listOf(
GenreItem("Incest", "incest"), Pair("Chinese", "zh"),
GenreItem("Isekai", "isekai"), Pair("English", "en"),
GenreItem("Kids", "kids"), Pair("Japanese", "ja"),
GenreItem("Loli", "loli"), Pair("Korean", "ko"),
GenreItem("Lolicon", "lolicon"), )
GenreItem("Magic", "magic"), }
GenreItem("Magical Girls", "magical_girls"), }
GenreItem("Martial Arts", "martial_arts"),
GenreItem("Mecha", "mecha"), class OriginalStatusFilter : StatusFilter("Original Work Status")
GenreItem("Medical", "medical"),
GenreItem("Military", "military"), class UploadStatusFilter : StatusFilter("Upload Status")
GenreItem("Monster Girls", "monster_girls"),
GenreItem("Monsters", "monsters"), class ChapterCountFilter : SelectFilter("Number of Chapters", chapters) {
GenreItem("Music", "music"), companion object {
GenreItem("Mystery", "mystery"), private val chapters = listOf(
GenreItem("Office Workers", "office_workers"), Pair("", ""),
GenreItem("Oneshot", "oneshot"), Pair("0", "0"),
GenreItem("Parody", "parody"), Pair("1+", "1"),
GenreItem("Philosophical", "philosophical"), Pair("10+", "10"),
GenreItem("Police", "police"), Pair("20+", "20"),
GenreItem("Post Apocalyptic", "post_apocalyptic"), Pair("30+", "30"),
GenreItem("Psychological", "psychological"), Pair("40+", "40"),
GenreItem("Reincarnation", "reincarnation"), Pair("50+", "50"),
GenreItem("Romance", "romance"), Pair("60+", "60"),
GenreItem("Samurai", "samurai"), Pair("70+", "70"),
GenreItem("School Life", "school_life"), Pair("80+", "80"),
GenreItem("Sci-fi", "sci_fi"), Pair("90+", "90"),
GenreItem("Shotacon", "shotacon"), Pair("100+", "100"),
GenreItem("Shounen Ai", "shounen_ai"), Pair("200+", "200"),
GenreItem("Shoujo Ai", "shoujo_ai"), Pair("300+", "300"),
GenreItem("Slice of Life", "slice_of_life"), Pair("299~200", "200-299"),
GenreItem("Space", "space"), Pair("199~100", "100-199"),
GenreItem("Sports", "sports"), Pair("99~90", "90-99"),
GenreItem("Super Power", "super_power"), Pair("89~80", "80-89"),
GenreItem("Superhero", "superhero"), Pair("79~70", "70-79"),
GenreItem("Supernatural", "supernatural"), Pair("69~60", "60-69"),
GenreItem("Survival", "survival"), Pair("59~50", "50-59"),
GenreItem("Thriller", "thriller"), Pair("49~40", "40-49"),
GenreItem("Traditional Games", "traditional_games"), Pair("39~30", "30-39"),
GenreItem("Tragedy", "tragedy"), Pair("29~20", "20-29"),
GenreItem("Vampires", "vampires"), Pair("19~10", "10-19"),
GenreItem("Video Games", "video_games"), Pair("9~1", "1-9"),
GenreItem("Virtual Reality", "virtual_reality"), )
GenreItem("Wuxia", "wuxia"), }
GenreItem("Yaoi", "yaoi"),
GenreItem("Yuri", "yuri"),
GenreItem("Zombies", "zombies"),
)
class GenreFilter(name: String, genreList: List<GenreItem>) :
Filter.Group<GenreItem>(name, genreList)
} }

View File

@ -0,0 +1,31 @@
package eu.kanade.tachiyomi.extension.all.mangapark
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class GraphQL<T>(
val variables: T,
val query: String,
)
@Serializable
data class SearchVariables(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,
)
@Serializable
data class IdVariables(val id: String)

View File

@ -0,0 +1,100 @@
package eu.kanade.tachiyomi.extension.all.mangapark
private fun buildQuery(queryAction: () -> String): String {
return queryAction()
.trimIndent()
.replace("%", "$")
}
val SEARCH_QUERY = buildQuery {
"""
query (
%select: SearchComic_Select
) {
get_searchComic(
select: %select
) {
items {
data {
id
name
altNames
artists
authors
genres
originalStatus
uploadStatus
summary
urlCoverOri
urlPath
}
}
}
}
"""
}
val DETAILS_QUERY = buildQuery {
"""
query(
%id: ID!
) {
get_comicNode(
id: %id
) {
data {
id
name
altNames
artists
authors
genres
originalStatus
uploadStatus
summary
urlCoverOri
urlPath
}
}
}
"""
}
val CHAPTERS_QUERY = buildQuery {
"""
query(
%id: ID!
) {
get_comicChapterList(
comicId: %id
) {
data {
id
dname
title
dateModify
dateCreate
urlPath
}
}
}
"""
}
val PAGES_QUERY = buildQuery {
"""
query(
%id: ID!
) {
get_chapterNode(
id: %id
) {
data {
imageFile {
urlList
}
}
}
}
"""
}

View File

@ -1,51 +0,0 @@
package eu.kanade.tachiyomi.extension.all.mangapark
import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Intent
import android.os.Bundle
import android.util.Log
import kotlin.system.exitProcess
class MangaParkUrlActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val host = intent?.data?.host
val pathSegments = intent?.data?.pathSegments
if (host != null && pathSegments != null) {
val query = fromGuya(pathSegments)
if (query == null) {
Log.e("MangaParkUrlActivity", "Unable to parse URI from intent $intent")
finish()
exitProcess(1)
}
val mainIntent = Intent().apply {
action = "eu.kanade.tachiyomi.SEARCH"
putExtra("query", query)
putExtra("filter", packageName)
}
try {
startActivity(mainIntent)
} catch (e: ActivityNotFoundException) {
Log.e("MangaParkUrlActivity", e.toString())
}
}
finish()
exitProcess(0)
}
private fun fromGuya(pathSegments: MutableList<String>): String? {
return if (pathSegments.size >= 2) {
val id = pathSegments[1]
"id:$id"
} else {
null
}
}
}