Yeet old MangaPark (#16215)

This commit is contained in:
AntsyLich 2023-04-29 07:52:38 +06:00 committed by GitHub
parent 53dafc1a1b
commit b1fc79fe9f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 0 additions and 648 deletions

View File

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="eu.kanade.tachiyomi.extension" />

View File

@ -1,20 +0,0 @@
# MangaPark
Table of Content
- [FAQ](#FAQ)
[Uncomment this if needed; and replace &#40; and &#41; with ( and )]: <> (- [Guides]&#40;#Guides&#41;)
Don't find the question you are look for go check out our general FAQs and Guides over at [Extension FAQ](https://tachiyomi.org/help/faq/#extensions) or [Getting Started](https://tachiyomi.org/help/guides/getting-started/#installation)
## FAQ
### How do I deal with duplicate chapters?
To solve this issue, follow the below steps.
1. Go to **Browse****Extensions**.
1. Click on **MangaPark** extension and then **Chapter List Source**.
1. Choose an option like **Smart list** or **Prioritize source**.
1. Go back to **MangaPark**'s chapter list and refresh it.
[Uncomment this if needed]: <> (## Guides)

View File

@ -1,12 +0,0 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext {
extName = 'MangaPark'
pkgNameSuffix = 'en.mangapark'
extClass = '.MangaPark'
extVersionCode = 23
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

View File

@ -1,614 +0,0 @@
package eu.kanade.tachiyomi.extension.en.mangapark
import android.annotation.SuppressLint
import android.app.Application
import android.content.SharedPreferences
import android.net.Uri
import eu.kanade.tachiyomi.network.GET
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.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.util.asJsoup
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.CacheControl
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
class MangaPark : ConfigurableSource, ParsedHttpSource() {
override val lang = "en"
override val client = network.cloudflareClient
override val supportsLatest = true
override val name = "MangaPark"
override val baseUrl = "https://v2.mangapark.net"
private val nextPageSelector = ".paging:not(.order) > li:last-child > a"
private val json: Json by injectLazy()
private val dateFormat = SimpleDateFormat("MMM d, yyyy, HH:mm a", Locale.ENGLISH)
private val dateFormatTimeOnly = SimpleDateFormat("HH:mm a", Locale.ENGLISH)
override fun popularMangaRequest(page: Int) = GET("$baseUrl/search?orderby=views_a&page=$page")
override fun popularMangaSelector() = searchMangaSelector()
override fun popularMangaFromElement(element: Element) = mangaFromElement(element)
override fun popularMangaNextPageSelector() = nextPageSelector
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/latest" + if (page > 1) "/$page" else "")
override fun latestUpdatesSelector() = ".ls1 .item"
override fun latestUpdatesFromElement(element: Element) = mangaFromElement(element)
override fun latestUpdatesNextPageSelector() = nextPageSelector
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val uri = Uri.parse("$baseUrl/search").buildUpon()
if (query.isNotEmpty()) {
uri.appendQueryParameter("q", query)
}
filters.forEach {
if (it is UriFilter) {
it.addToUri(uri)
}
}
if (page != 1) {
uri.appendQueryParameter("page", page.toString())
}
return GET(uri.toString())
}
override fun searchMangaSelector() = ".item"
override fun searchMangaFromElement(element: Element) = mangaFromElement(element)
override fun searchMangaNextPageSelector() = nextPageSelector
private fun mangaFromElement(element: Element) = SManga.create().apply {
val coverElement = element.getElementsByClass("cover").first()!!
url = coverElement.attr("href")
title = coverElement.attr("title")
thumbnail_url = coverElement.select("img").attr("abs:src")
}
@SuppressLint("DefaultLocale")
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
document.select(".cover > img").first()!!.let { coverElement ->
title = coverElement.attr("title")
thumbnail_url = coverElement.attr("abs:src")
}
document.select(".attr > tbody > tr").forEach {
when (it.getElementsByTag("th").first()!!.text().trim().lowercase()) {
"author(s)" -> {
author = it.getElementsByTag("a").joinToString(transform = Element::text)
}
"artist(s)" -> {
artist = it.getElementsByTag("a").joinToString(transform = Element::text)
}
"genre(s)" -> {
genre = it.getElementsByTag("a").joinToString(transform = Element::text)
}
"status" -> {
status = when (it.getElementsByTag("td").text().trim().lowercase()) {
"ongoing" -> SManga.ONGOING
"completed" -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
}
}
}
description = document.getElementsByClass("summary").text().trim()
// add alternative name to manga description
val altName = "Alternative Name: "
document.select(".attr > tbody > tr:contains(Alter) td").firstOrNull()?.ownText()?.let {
if (it.isBlank().not()) {
description = when {
description.isNullOrBlank() -> altName + it
else -> description + "\n\n$altName" + it
}
}
}
}
// force network to make sure chapter prefs take effect
override fun chapterListRequest(manga: SManga): Request {
return GET(baseUrl + manga.url, headers, CacheControl.FORCE_NETWORK)
}
override fun chapterListParse(response: Response): List<SChapter> {
fun List<SChapter>.getMissingChapters(allChapters: List<SChapter>): List<SChapter> {
val chapterNums = this.map { it.chapter_number }
return allChapters.filter { it.chapter_number !in chapterNums }.distinctBy { it.chapter_number }
}
fun List<SChapter>.filterOrAll(source: String): List<SChapter> {
val chapters = this.filter { it.scanlator!!.contains(source) }
return if (chapters.isNotEmpty()) {
(chapters + chapters.getMissingChapters(this)).sortedByDescending { it.chapter_number }
} else {
this
}
}
val mangaBySource = response.asJsoup().select("div[id^=stream]")
.map { sourceElement ->
var lastNum = 0F
val sourceName = sourceElement.select("i + span").text()
sourceElement.select(chapterListSelector())
.reversed() // so incrementing lastNum works
.map { chapterElement ->
chapterFromElement(chapterElement, sourceName, lastNum)
.also { lastNum = it.chapter_number }
}
.distinctBy { it.chapter_number } // there's even duplicate chapters within a source ( -.- )
}
return when (getSourcePref()) {
// source with most chapters along with chapters that source doesn't have
"most" -> {
val chapters = mangaBySource.maxByOrNull { it.count() }!!
(chapters + chapters.getMissingChapters(mangaBySource.flatten())).sortedByDescending { it.chapter_number }
}
// "smart list" - try not to miss a chapter and avoid dupes
"smart" -> mangaBySource.flatten().distinctBy { it.chapter_number }.sortedByDescending { it.chapter_number }
// use a specific source + any missing chapters, display all if none available from that source
"rock" -> mangaBySource.flatten().filterOrAll("Rock")
"duck" -> mangaBySource.flatten().filterOrAll("Duck")
"mini" -> mangaBySource.flatten().filterOrAll("Mini")
"fox" -> mangaBySource.flatten().filterOrAll("Fox")
"panda" -> mangaBySource.flatten().filterOrAll("Panda")
// all sources, all chapters
else -> mangaBySource.flatMap { it.reversed() }
}
}
override fun chapterListSelector() = ".volume .chapter li"
private val chapterNumberRegex = Regex("""\b\d+\.?\d?\b""")
private fun chapterFromElement(element: Element, source: String, lastNum: Float): SChapter {
fun Float.incremented() = this + .00001F
fun Float?.orIncrementLastNum() = if (this == null || this < lastNum) lastNum.incremented() else this
return SChapter.create().apply {
element.select(".tit > a").first()!!.let {
url = it.attr("href").removeSuffix("1")
name = it.text()
}
// Get the chapter number or create a unique one if it's not available
chapter_number = chapterNumberRegex.findAll(name)
.toList()
.map { it.value.toFloatOrNull() }
.let { nums ->
when {
nums.count() == 1 -> nums[0].orIncrementLastNum()
nums.count() >= 2 -> nums[1].orIncrementLastNum()
else -> lastNum.incremented()
}
}
date_upload = parseDate(element.select(".time").first()!!.text().trim())
scanlator = source
}
}
override fun chapterFromElement(element: Element): SChapter = throw UnsupportedOperationException("Not used")
@SuppressLint("DefaultLocale")
private fun parseDate(date: String): Long {
val lcDate = date.lowercase()
if (lcDate.endsWith("ago")) return parseRelativeDate(lcDate)
// Handle 'yesterday' and 'today'
var relativeDate: Calendar? = null
if (lcDate.startsWith("yesterday")) {
relativeDate = Calendar.getInstance()
relativeDate.add(Calendar.DAY_OF_MONTH, -1) // yesterday
} else if (lcDate.startsWith("today")) {
relativeDate = Calendar.getInstance()
}
relativeDate?.let {
// Since the date is not specified, it defaults to 1970!
val time = dateFormatTimeOnly.parse(lcDate.substringAfter(' ')) ?: return 0
val cal = Calendar.getInstance()
cal.time = time
// Copy time to relative date
it.set(Calendar.HOUR_OF_DAY, cal.get(Calendar.HOUR_OF_DAY))
it.set(Calendar.MINUTE, cal.get(Calendar.MINUTE))
return it.timeInMillis
}
return dateFormat.parse(lcDate)?.time ?: 0
}
/**
* Parses dates in this form:
* `11 days ago`
*/
private fun parseRelativeDate(date: String): Long {
val trimmedDate = date.split(" ")
if (trimmedDate[2] != "ago") return 0
val number = when (trimmedDate[0]) {
"a" -> 1
else -> trimmedDate[0].toIntOrNull() ?: return 0
}
val unit = trimmedDate[1].removeSuffix("s") // Remove 's' suffix
val now = Calendar.getInstance()
// Map English unit to Java unit
val javaUnit = when (unit) {
"year" -> Calendar.YEAR
"month" -> Calendar.MONTH
"week" -> Calendar.WEEK_OF_MONTH
"day" -> Calendar.DAY_OF_MONTH
"hour" -> Calendar.HOUR
"minute" -> Calendar.MINUTE
"second" -> Calendar.SECOND
else -> return 0
}
now.add(javaUnit, -number)
return now.timeInMillis
}
private val objRegex = Regex("""var _load_pages = (\[.*])""")
override fun pageListParse(response: Response): List<Page> {
val obj = objRegex.find(response.body.string())?.groupValues?.get(1)
?: throw Exception("_load_pages not found - ${response.request.url}")
return json.parseToJsonElement(obj).jsonArray.mapIndexed { i, it ->
val url = it.jsonObject["u"]!!.jsonPrimitive.content
Page(i, imageUrl = if (url.startsWith("//")) "https://$url" else url)
}
}
override fun pageListParse(document: Document): List<Page> = throw UnsupportedOperationException("Not used")
// Unused, we can get image urls directly from the chapter page
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used")
override fun getFilterList() = FilterList(
AuthorArtistText(),
SearchTypeFilter("Title query", "name-match"),
SearchTypeFilter("Author/Artist query", "autart-match"),
SortFilter(),
GenreGroup(),
GenreInclusionFilter(),
ChapterCountFilter(),
StatusFilter(),
RatingFilter(),
TypeFilter(),
YearFilter(),
)
private class SearchTypeFilter(name: String, val uriParam: String) :
Filter.Select<String>(name, STATE_MAP), UriFilter {
override fun addToUri(uri: Uri.Builder) {
if (STATE_MAP[state] != "contain") {
uri.appendQueryParameter(uriParam, STATE_MAP[state])
}
}
companion object {
private val STATE_MAP = arrayOf("contain", "begin", "end")
}
}
private class AuthorArtistText : Filter.Text("Author/Artist"), UriFilter {
override fun addToUri(uri: Uri.Builder) {
if (state.isNotEmpty()) {
uri.appendQueryParameter("autart", state)
}
}
}
private class GenreFilter(val uriParam: String, displayName: String) : Filter.TriState(displayName)
private class GenreGroup :
Filter.Group<GenreFilter>(
"Genres",
listOf(
GenreFilter("4-koma", "4 koma"),
GenreFilter("action", "Action"),
GenreFilter("adaptation", "Adaptation"),
GenreFilter("adult", "Adult"),
GenreFilter("adventure", "Adventure"),
GenreFilter("aliens", "Aliens"),
GenreFilter("animals", "Animals"),
GenreFilter("anthology", "Anthology"),
GenreFilter("award-winning", "Award winning"),
GenreFilter("comedy", "Comedy"),
GenreFilter("cooking", "Cooking"),
GenreFilter("crime", "Crime"),
GenreFilter("crossdressing", "Crossdressing"),
GenreFilter("delinquents", "Delinquents"),
GenreFilter("demons", "Demons"),
GenreFilter("doujinshi", "Doujinshi"),
GenreFilter("drama", "Drama"),
GenreFilter("ecchi", "Ecchi"),
GenreFilter("fan-colored", "Fan colored"),
GenreFilter("fantasy", "Fantasy"),
GenreFilter("food", "Food"),
GenreFilter("full-color", "Full color"),
GenreFilter("game", "Game"),
GenreFilter("gender-bender", "Gender bender"),
GenreFilter("genderswap", "Genderswap"),
GenreFilter("ghosts", "Ghosts"),
GenreFilter("gore", "Gore"),
GenreFilter("gossip", "Gossip"),
GenreFilter("gyaru", "Gyaru"),
GenreFilter("harem", "Harem"),
GenreFilter("historical", "Historical"),
GenreFilter("horror", "Horror"),
GenreFilter("incest", "Incest"),
GenreFilter("isekai", "Isekai"),
GenreFilter("josei", "Josei"),
GenreFilter("kids", "Kids"),
GenreFilter("loli", "Loli"),
GenreFilter("lolicon", "Lolicon"),
GenreFilter("long-strip", "Long strip"),
GenreFilter("mafia", "Mafia"),
GenreFilter("magic", "Magic"),
GenreFilter("magical-girls", "Magical girls"),
GenreFilter("manhwa", "Manhwa"),
GenreFilter("martial-arts", "Martial arts"),
GenreFilter("mature", "Mature"),
GenreFilter("mecha", "Mecha"),
GenreFilter("medical", "Medical"),
GenreFilter("military", "Military"),
GenreFilter("monster-girls", "Monster girls"),
GenreFilter("monsters", "Monsters"),
GenreFilter("music", "Music"),
GenreFilter("mystery", "Mystery"),
GenreFilter("ninja", "Ninja"),
GenreFilter("office-workers", "Office workers"),
GenreFilter("official-colored", "Official colored"),
GenreFilter("one-shot", "One shot"),
GenreFilter("parody", "Parody"),
GenreFilter("philosophical", "Philosophical"),
GenreFilter("police", "Police"),
GenreFilter("post-apocalyptic", "Post apocalyptic"),
GenreFilter("psychological", "Psychological"),
GenreFilter("reincarnation", "Reincarnation"),
GenreFilter("reverse-harem", "Reverse harem"),
GenreFilter("romance", "Romance"),
GenreFilter("samurai", "Samurai"),
GenreFilter("school-life", "School life"),
GenreFilter("sci-fi", "Sci fi"),
GenreFilter("seinen", "Seinen"),
GenreFilter("shota", "Shota"),
GenreFilter("shotacon", "Shotacon"),
GenreFilter("shoujo", "Shoujo"),
GenreFilter("shoujo-ai", "Shoujo ai"),
GenreFilter("shounen", "Shounen"),
GenreFilter("shounen-ai", "Shounen ai"),
GenreFilter("slice-of-life", "Slice of life"),
GenreFilter("smut", "Smut"),
GenreFilter("space", "Space"),
GenreFilter("sports", "Sports"),
GenreFilter("super-power", "Super power"),
GenreFilter("superhero", "Superhero"),
GenreFilter("supernatural", "Supernatural"),
GenreFilter("survival", "Survival"),
GenreFilter("suspense", "Suspense"),
GenreFilter("thriller", "Thriller"),
GenreFilter("time-travel", "Time travel"),
GenreFilter("toomics", "Toomics"),
GenreFilter("traditional-games", "Traditional games"),
GenreFilter("tragedy", "Tragedy"),
GenreFilter("user-created", "User created"),
GenreFilter("vampire", "Vampire"),
GenreFilter("vampires", "Vampires"),
GenreFilter("video-games", "Video games"),
GenreFilter("virtual-reality", "Virtual reality"),
GenreFilter("web-comic", "Web comic"),
GenreFilter("webtoon", "Webtoon"),
GenreFilter("wuxia", "Wuxia"),
GenreFilter("yaoi", "Yaoi"),
GenreFilter("yuri", "Yuri"),
GenreFilter("zombies", "Zombies"),
),
),
UriFilter {
override fun addToUri(uri: Uri.Builder) {
val genresParameterValue = state.filter { it.isIncluded() }.joinToString(",") { it.uriParam }
if (genresParameterValue.isNotEmpty()) {
uri.appendQueryParameter("genres", genresParameterValue)
}
val genresExcludeParameterValue = state.filter { it.isExcluded() }.joinToString(",") { it.uriParam }
if (genresExcludeParameterValue.isNotEmpty()) {
uri.appendQueryParameter("genres-exclude", genresExcludeParameterValue)
}
}
}
private class GenreInclusionFilter : UriSelectFilter(
"Genre inclusion",
"genres-mode",
arrayOf(
Pair("and", "And mode"),
Pair("or", "Or mode"),
),
)
private class ChapterCountFilter : UriSelectFilter(
"Chapter count",
"chapters",
arrayOf(
Pair("any", "Any"),
Pair("1", "1 +"),
Pair("5", "5 +"),
Pair("10", "10 +"),
Pair("20", "20 +"),
Pair("30", "30 +"),
Pair("40", "40 +"),
Pair("50", "50 +"),
Pair("100", "100 +"),
Pair("150", "150 +"),
Pair("200", "200 +"),
),
)
private class StatusFilter : UriSelectFilter(
"Status",
"status",
arrayOf(
Pair("any", "Any"),
Pair("completed", "Completed"),
Pair("ongoing", "Ongoing"),
),
)
private class RatingFilter : UriSelectFilter(
"Rating",
"rating",
arrayOf(
Pair("any", "Any"),
Pair("5", "5 stars"),
Pair("4", "4 stars"),
Pair("3", "3 stars"),
Pair("2", "2 stars"),
Pair("1", "1 star"),
Pair("0", "0 stars"),
),
)
private class TypeFilter : UriSelectFilter(
"Type",
"types",
arrayOf(
Pair("any", "Any"),
Pair("manga", "Japanese Manga"),
Pair("manhwa", "Korean Manhwa"),
Pair("manhua", "Chinese Manhua"),
Pair("unknown", "Unknown"),
),
)
private class YearFilter : UriSelectFilter(
"Release year",
"years",
arrayOf(
Pair("any", "Any"),
// Get all years between today and 1946
*(Calendar.getInstance().get(Calendar.YEAR) downTo 1946).map {
Pair(it.toString(), it.toString())
}.toTypedArray(),
),
)
private class SortFilter : UriSelectFilter(
"Sort",
"orderby",
arrayOf(
Pair("a-z", "A-Z"),
Pair("views_a", "Views all-time"),
Pair("views_y", "Views last 365 days"),
Pair("views_s", "Views last 180 days"),
Pair("views_t", "Views last 90 days"),
Pair("rating", "Rating"),
Pair("update", "Latest"),
Pair("create", "New manga"),
),
firstIsUnspecified = false,
defaultValue = 1,
)
/**
* Class that creates a select filter. Each entry in the dropdown has a name and a display name.
* If an entry is selected it is appended as a query parameter onto the end of the URI.
* If `firstIsUnspecified` is set to true, if the first entry is selected, nothing will be appended on the the URI.
*/
// vals: <name, display>
private open class UriSelectFilter(
displayName: String,
val uriParam: String,
val vals: Array<Pair<String, String>>,
val firstIsUnspecified: Boolean = true,
defaultValue: Int = 0,
) :
Filter.Select<String>(displayName, vals.map { it.second }.toTypedArray(), defaultValue), UriFilter {
override fun addToUri(uri: Uri.Builder) {
if (state != 0 || !firstIsUnspecified) {
uri.appendQueryParameter(uriParam, vals[state].first)
}
}
}
/**
* Represents a filter that is able to modify a URI.
*/
private interface UriFilter {
fun addToUri(uri: Uri.Builder)
}
// Preferences
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) {
val myPref = androidx.preference.ListPreference(screen.context).apply {
key = SOURCE_PREF_TITLE
title = SOURCE_PREF_TITLE
entries = sourceArray.map { it.first }.toTypedArray()
entryValues = sourceArray.map { it.second }.toTypedArray()
summary = "%s"
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = this.findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(SOURCE_PREF, entry).commit()
}
}
screen.addPreference(myPref)
}
private fun getSourcePref(): String? = preferences.getString(SOURCE_PREF, "all")
companion object {
private const val SOURCE_PREF_TITLE = "Chapter List Source"
private const val SOURCE_PREF = "Manga_Park_Source"
private val sourceArray = arrayOf(
Pair("All sources, all chapters", "all"),
Pair("Source with most chapters", "most"),
Pair("Smart list", "smart"),
Pair("Prioritize source: Rock", "rock"),
Pair("Prioritize source: Duck", "duck"),
Pair("Prioritize source: Mini", "mini"),
Pair("Prioritize source: Fox", "fox"),
Pair("Prioritize source: Panda", "panda"),
)
}
}