Change and add Manga4Life
parent
ea4e69df19
commit
02581c9e2d
@ -0,0 +1,280 @@
|
|||||||
|
package org.koitharu.kotatsu.parsers.site.en
|
||||||
|
|
||||||
|
import okhttp3.Headers
|
||||||
|
import org.json.JSONArray
|
||||||
|
import org.json.JSONObject
|
||||||
|
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||||
|
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||||
|
import org.koitharu.kotatsu.parsers.PagedMangaParser
|
||||||
|
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||||
|
import org.koitharu.kotatsu.parsers.model.*
|
||||||
|
import org.koitharu.kotatsu.parsers.network.UserAgents
|
||||||
|
import org.koitharu.kotatsu.parsers.util.*
|
||||||
|
import org.koitharu.kotatsu.parsers.util.json.*
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.EnumSet
|
||||||
|
|
||||||
|
|
||||||
|
@MangaSourceParser("MANGA4LIFE", "Manga4Life", "en")
|
||||||
|
internal class Manga4Life(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.MANGA4LIFE, 0) {
|
||||||
|
|
||||||
|
override val sortOrders: Set<SortOrder> = EnumSet.of(SortOrder.ALPHABETICAL)
|
||||||
|
|
||||||
|
override val configKeyDomain = ConfigKey.Domain("manga4life.com")
|
||||||
|
|
||||||
|
override val headers: Headers = Headers.Builder()
|
||||||
|
.add("User-Agent", UserAgents.CHROME_DESKTOP)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
|
||||||
|
override suspend fun getListPage(
|
||||||
|
page: Int,
|
||||||
|
query: String?,
|
||||||
|
tags: Set<MangaTag>?,
|
||||||
|
sortOrder: SortOrder,
|
||||||
|
): List<Manga> {
|
||||||
|
|
||||||
|
if (page > 1) {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
val url = buildString {
|
||||||
|
append("https://$domain/search/")
|
||||||
|
}
|
||||||
|
val doc = webClient.httpGet(url).parseHtml()
|
||||||
|
val json = JSONArray(
|
||||||
|
doc.selectFirstOrThrow("script:containsData(MainFunction)").data()
|
||||||
|
.substringAfter("vm.Directory = ").substringBefore("vm.GetIntValue").trim()
|
||||||
|
.replace(";", " "),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
val manga = ArrayList<Manga>()
|
||||||
|
|
||||||
|
for (i in 0 until json.length()) {
|
||||||
|
val m = json.getJSONObject(i)
|
||||||
|
val href = "/manga/" + m.getString("i")
|
||||||
|
val imgUrl = "https://temp.compsci88.com/cover/" + m.getString("i") + ".jpg"
|
||||||
|
if (!query.isNullOrEmpty()) {
|
||||||
|
if (m.getString("i").contains(query.urlEncoded(), ignoreCase = true)) {
|
||||||
|
manga.add(
|
||||||
|
Manga(
|
||||||
|
id = generateUid(href),
|
||||||
|
title = m.getString("i").replace("-", " "),
|
||||||
|
altTitle = null,
|
||||||
|
url = href,
|
||||||
|
publicUrl = href.toAbsoluteUrl(domain),
|
||||||
|
rating = RATING_UNKNOWN,
|
||||||
|
isNsfw = false,
|
||||||
|
coverUrl = imgUrl,
|
||||||
|
tags = emptySet(),
|
||||||
|
state = null,
|
||||||
|
author = null,
|
||||||
|
source = source,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (!tags.isNullOrEmpty()) {
|
||||||
|
|
||||||
|
val a = m.getJSONArray("g").toString()
|
||||||
|
var found = true
|
||||||
|
tags.forEach {
|
||||||
|
if (!a.contains(it.key, ignoreCase = true)) {
|
||||||
|
found = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (found) {
|
||||||
|
manga.add(
|
||||||
|
Manga(
|
||||||
|
id = generateUid(href),
|
||||||
|
title = m.getString("i").replace("-", " "),
|
||||||
|
altTitle = null,
|
||||||
|
url = href,
|
||||||
|
publicUrl = href.toAbsoluteUrl(domain),
|
||||||
|
rating = RATING_UNKNOWN,
|
||||||
|
isNsfw = false,
|
||||||
|
coverUrl = imgUrl,
|
||||||
|
tags = emptySet(),
|
||||||
|
state = null,
|
||||||
|
author = null,
|
||||||
|
source = source,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
manga.add(
|
||||||
|
Manga(
|
||||||
|
id = generateUid(href),
|
||||||
|
title = m.getString("i").replace("-", " "),
|
||||||
|
altTitle = null,
|
||||||
|
url = href,
|
||||||
|
publicUrl = href.toAbsoluteUrl(domain),
|
||||||
|
rating = RATING_UNKNOWN,
|
||||||
|
isNsfw = false,
|
||||||
|
coverUrl = imgUrl,
|
||||||
|
tags = emptySet(),
|
||||||
|
state = null,
|
||||||
|
author = null,
|
||||||
|
source = source,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return manga
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getTags(): Set<MangaTag> {
|
||||||
|
val doc = webClient.httpGet("https://$domain/search/").parseHtml()
|
||||||
|
val tags = doc.selectFirstOrThrow("script:containsData(vm.AvailableFilters)").data()
|
||||||
|
.substringAfter("\"Genre\" \t\t: [").substringBefore("]").replace("'", "").split(",")
|
||||||
|
|
||||||
|
return tags.mapNotNullToSet { tag ->
|
||||||
|
MangaTag(
|
||||||
|
key = tag,
|
||||||
|
title = tag,
|
||||||
|
source = source,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getDetails(manga: Manga): Manga {
|
||||||
|
val doc = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
|
||||||
|
|
||||||
|
val chapter = JSONArray(
|
||||||
|
JSONArray(
|
||||||
|
doc.selectFirstOrThrow("script:containsData(MainFunction)").data()
|
||||||
|
.substringAfter("vm.Chapters = ").substringBefore(";"),
|
||||||
|
).toJSONList().reversed(),
|
||||||
|
)
|
||||||
|
|
||||||
|
val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:SS", sourceLocale)
|
||||||
|
|
||||||
|
return manga.copy(
|
||||||
|
altTitle = null,
|
||||||
|
state = when (doc.selectFirstOrThrow(".list-group-item:contains(Status:) a").text()) {
|
||||||
|
"Ongoing (Scan)", "Ongoing (Publish)" -> MangaState.ONGOING
|
||||||
|
"Complete (Scan)", "Complete (Publish)",
|
||||||
|
"Cancelled (Scan)", "Cancelled (Publish)",
|
||||||
|
"Discontinued (Scan)", "Discontinued (Publish)",
|
||||||
|
"Hiatus (Scan)", "Hiatus (Publish)",
|
||||||
|
-> MangaState.FINISHED
|
||||||
|
|
||||||
|
else -> null
|
||||||
|
},
|
||||||
|
tags = doc.select(".list-group-item:contains(Genre(s):) a").mapNotNullToSet { a ->
|
||||||
|
MangaTag(
|
||||||
|
key = a.attr("href").substringAfterLast("="),
|
||||||
|
title = a.text(),
|
||||||
|
source = source,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
author = doc.select(".list-group-item:contains(Author(s):) a").text(),
|
||||||
|
description = doc.selectFirstOrThrow(".top-5.Content").text(),
|
||||||
|
|
||||||
|
chapters = chapter.mapJSONIndexed { i, j ->
|
||||||
|
val indexChapter = j.getString("Chapter")!!
|
||||||
|
val url = "/read-online/" + manga.url.substringAfter("/manga/") + chapterURLEncode(indexChapter)
|
||||||
|
val name = j.getString("ChapterName").let {
|
||||||
|
if (it.isNullOrEmpty() || it == "null") "${j.getString("Type")} ${
|
||||||
|
chapterImage(
|
||||||
|
indexChapter,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
}" else it
|
||||||
|
}
|
||||||
|
val date = j.getString("Date")
|
||||||
|
MangaChapter(
|
||||||
|
id = generateUid(url),
|
||||||
|
name = name,
|
||||||
|
number = i + 1,
|
||||||
|
url = url,
|
||||||
|
scanlator = null,
|
||||||
|
uploadDate = dateFormat.tryParse(date),
|
||||||
|
branch = null,
|
||||||
|
source = source,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun chapterURLEncode(e: String): String {
|
||||||
|
var index = ""
|
||||||
|
val t = e.substring(0, 1).toInt()
|
||||||
|
if (1 != t) {
|
||||||
|
index = "-index-$t"
|
||||||
|
}
|
||||||
|
val dgt = if (e.toInt() < 100100) {
|
||||||
|
4
|
||||||
|
} else if (e.toInt() < 101000) {
|
||||||
|
3
|
||||||
|
} else if (e.toInt() < 110000) {
|
||||||
|
2
|
||||||
|
} else {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
val n = e.substring(dgt, e.length - 1)
|
||||||
|
var suffix = ""
|
||||||
|
val path = e.substring(e.length - 1).toInt()
|
||||||
|
if (0 != path) {
|
||||||
|
suffix = ".$path"
|
||||||
|
}
|
||||||
|
return "-chapter-$n$suffix$index.html"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val chapterImageRegex = Regex("""^0+""")
|
||||||
|
|
||||||
|
private fun chapterImage(e: String, cleanString: Boolean = false): String {
|
||||||
|
// cleanString will result in an empty string if chapter number is 0, hence the else if below
|
||||||
|
val a = e.substring(1, e.length - 1).let { if (cleanString) it.replace(chapterImageRegex, "") else it }
|
||||||
|
// If b is not zero, indicates chapter has decimal numbering
|
||||||
|
val b = e.substring(e.length - 1).toInt()
|
||||||
|
return if (b == 0 && a.isNotEmpty()) {
|
||||||
|
a
|
||||||
|
} else if (b == 0 && a.isEmpty()) {
|
||||||
|
"0"
|
||||||
|
} else {
|
||||||
|
"$a.$b"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
||||||
|
val fullUrl = chapter.url.toAbsoluteUrl(domain)
|
||||||
|
val doc = webClient.httpGet(fullUrl).parseHtml()
|
||||||
|
val script = doc.selectFirstOrThrow("script:containsData(MainFunction)").data()
|
||||||
|
val curChapter = JSONObject(
|
||||||
|
doc.selectFirstOrThrow("script:containsData(MainFunction)").data().substringAfter("vm.CurChapter = ")
|
||||||
|
.substringBefore(";"),
|
||||||
|
)
|
||||||
|
val pageTotal = curChapter.getString("Page")!!.toInt()
|
||||||
|
val host = "https://" +
|
||||||
|
script
|
||||||
|
.substringAfter("vm.CurPathName = \"", "")
|
||||||
|
.substringBefore("\"")
|
||||||
|
.also {
|
||||||
|
if (it.isEmpty()) {
|
||||||
|
throw Exception("Manga4Life is overloaded and blocking Tachiyomi right now. Wait for unblock.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val titleURI = script.substringAfter("vm.IndexName = \"").substringBefore("\"")
|
||||||
|
val seasonURI = curChapter.getString("Directory")!!
|
||||||
|
.let { if (it.isEmpty()) "" else "$it/" }
|
||||||
|
val path = "$host/manga/$titleURI/$seasonURI"
|
||||||
|
val chNum = chapterImage(curChapter.getString("Chapter")!!)
|
||||||
|
|
||||||
|
return IntRange(1, pageTotal).mapIndexed { i, _ ->
|
||||||
|
val imageNum = (i + 1).toString().let { "000$it" }.let { it.substring(it.length - 3) }
|
||||||
|
val url = "$path$chNum-$imageNum.png"
|
||||||
|
MangaPage(
|
||||||
|
id = generateUid(url),
|
||||||
|
url = url,
|
||||||
|
preview = null,
|
||||||
|
source = source,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue