|
|
|
|
@ -4,19 +4,21 @@ import okhttp3.Headers
|
|
|
|
|
import org.json.JSONArray
|
|
|
|
|
import org.json.JSONObject
|
|
|
|
|
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
|
|
|
|
import org.koitharu.kotatsu.parsers.MangaParser
|
|
|
|
|
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 org.koitharu.kotatsu.parsers.util.json.getStringOrNull
|
|
|
|
|
import org.koitharu.kotatsu.parsers.util.json.mapJSONIndexed
|
|
|
|
|
import org.koitharu.kotatsu.parsers.util.json.toJSONList
|
|
|
|
|
import java.text.SimpleDateFormat
|
|
|
|
|
import java.util.EnumSet
|
|
|
|
|
import java.util.*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@MangaSourceParser("MANGA4LIFE", "Manga4Life", "en")
|
|
|
|
|
internal class Manga4Life(context: MangaLoaderContext) : PagedMangaParser(context, MangaSource.MANGA4LIFE, 0) {
|
|
|
|
|
internal class Manga4Life(context: MangaLoaderContext) : MangaParser(context, MangaSource.MANGA4LIFE) {
|
|
|
|
|
|
|
|
|
|
override val sortOrders: Set<SortOrder> = EnumSet.of(SortOrder.ALPHABETICAL)
|
|
|
|
|
|
|
|
|
|
@ -26,30 +28,19 @@ internal class Manga4Life(context: MangaLoaderContext) : PagedMangaParser(contex
|
|
|
|
|
.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) {
|
|
|
|
|
override suspend fun getList(offset: Int, query: String?, tags: Set<MangaTag>?, sortOrder: SortOrder): List<Manga> {
|
|
|
|
|
if (offset > 0) {
|
|
|
|
|
return emptyList()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val url = buildString {
|
|
|
|
|
append("https://$domain/search/")
|
|
|
|
|
}
|
|
|
|
|
val doc = webClient.httpGet(url).parseHtml()
|
|
|
|
|
val doc = webClient.httpGet("https://$domain/search/").parseHtml()
|
|
|
|
|
val json = JSONArray(
|
|
|
|
|
doc.selectFirstOrThrow("script:containsData(MainFunction)").data()
|
|
|
|
|
.substringAfter("vm.Directory = ").substringBefore("vm.GetIntValue").trim()
|
|
|
|
|
.replace(";", " "),
|
|
|
|
|
.replace(';', ' '),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
val manga = ArrayList<Manga>()
|
|
|
|
|
val manga = ArrayList<Manga>(json.length())
|
|
|
|
|
|
|
|
|
|
for (i in 0 until json.length()) {
|
|
|
|
|
val m = json.getJSONObject(i)
|
|
|
|
|
@ -60,7 +51,7 @@ internal class Manga4Life(context: MangaLoaderContext) : PagedMangaParser(contex
|
|
|
|
|
manga.add(
|
|
|
|
|
Manga(
|
|
|
|
|
id = generateUid(href),
|
|
|
|
|
title = m.getString("i").replace("-", " "),
|
|
|
|
|
title = m.getString("i").replace('-', ' '),
|
|
|
|
|
altTitle = null,
|
|
|
|
|
url = href,
|
|
|
|
|
publicUrl = href.toAbsoluteUrl(domain),
|
|
|
|
|
@ -88,7 +79,7 @@ internal class Manga4Life(context: MangaLoaderContext) : PagedMangaParser(contex
|
|
|
|
|
manga.add(
|
|
|
|
|
Manga(
|
|
|
|
|
id = generateUid(href),
|
|
|
|
|
title = m.getString("i").replace("-", " "),
|
|
|
|
|
title = m.getString("i").replace('-', ' '),
|
|
|
|
|
altTitle = null,
|
|
|
|
|
url = href,
|
|
|
|
|
publicUrl = href.toAbsoluteUrl(domain),
|
|
|
|
|
@ -106,7 +97,7 @@ internal class Manga4Life(context: MangaLoaderContext) : PagedMangaParser(contex
|
|
|
|
|
manga.add(
|
|
|
|
|
Manga(
|
|
|
|
|
id = generateUid(href),
|
|
|
|
|
title = m.getString("i").replace("-", " "),
|
|
|
|
|
title = m.getString("i").replace('-', ' '),
|
|
|
|
|
altTitle = null,
|
|
|
|
|
url = href,
|
|
|
|
|
publicUrl = href.toAbsoluteUrl(domain),
|
|
|
|
|
@ -130,9 +121,12 @@ internal class Manga4Life(context: MangaLoaderContext) : PagedMangaParser(contex
|
|
|
|
|
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(",")
|
|
|
|
|
.substringAfter("\"Genre\" \t\t: [")
|
|
|
|
|
.substringBefore(']')
|
|
|
|
|
.replace("'", "")
|
|
|
|
|
.split(',')
|
|
|
|
|
|
|
|
|
|
return tags.mapNotNullToSet { tag ->
|
|
|
|
|
return tags.mapToSet { tag ->
|
|
|
|
|
MangaTag(
|
|
|
|
|
key = tag,
|
|
|
|
|
title = tag,
|
|
|
|
|
@ -147,7 +141,7 @@ internal class Manga4Life(context: MangaLoaderContext) : PagedMangaParser(contex
|
|
|
|
|
val chapter = JSONArray(
|
|
|
|
|
JSONArray(
|
|
|
|
|
doc.selectFirstOrThrow("script:containsData(MainFunction)").data()
|
|
|
|
|
.substringAfter("vm.Chapters = ").substringBefore(";"),
|
|
|
|
|
.substringAfter("vm.Chapters = ").substringBefore(';'),
|
|
|
|
|
).toJSONList().reversed(),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@ -167,7 +161,7 @@ internal class Manga4Life(context: MangaLoaderContext) : PagedMangaParser(contex
|
|
|
|
|
},
|
|
|
|
|
tags = doc.select(".list-group-item:contains(Genre(s):) a").mapNotNullToSet { a ->
|
|
|
|
|
MangaTag(
|
|
|
|
|
key = a.attr("href").substringAfterLast("="),
|
|
|
|
|
key = a.attr("href").substringAfterLast('='),
|
|
|
|
|
title = a.text(),
|
|
|
|
|
source = source,
|
|
|
|
|
)
|
|
|
|
|
@ -178,7 +172,7 @@ internal class Manga4Life(context: MangaLoaderContext) : PagedMangaParser(contex
|
|
|
|
|
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 {
|
|
|
|
|
val name = j.getStringOrNull("ChapterName").let {
|
|
|
|
|
if (it.isNullOrEmpty() || it == "null") "${j.getString("Type")} ${
|
|
|
|
|
chapterImage(
|
|
|
|
|
indexChapter,
|
|
|
|
|
@ -186,7 +180,7 @@ internal class Manga4Life(context: MangaLoaderContext) : PagedMangaParser(contex
|
|
|
|
|
)
|
|
|
|
|
}" else it
|
|
|
|
|
}
|
|
|
|
|
val date = j.getString("Date")
|
|
|
|
|
val date = j.getStringOrNull("Date")
|
|
|
|
|
MangaChapter(
|
|
|
|
|
id = generateUid(url),
|
|
|
|
|
name = name,
|
|
|
|
|
@ -207,14 +201,12 @@ internal class Manga4Life(context: MangaLoaderContext) : PagedMangaParser(contex
|
|
|
|
|
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 ei = e.toInt()
|
|
|
|
|
val dgt = when {
|
|
|
|
|
ei < 100100 -> 4
|
|
|
|
|
ei < 101000 -> 3
|
|
|
|
|
ei < 110000 -> 2
|
|
|
|
|
else -> 1
|
|
|
|
|
}
|
|
|
|
|
val n = e.substring(dgt, e.length - 1)
|
|
|
|
|
var suffix = ""
|
|
|
|
|
@ -232,12 +224,10 @@ internal class Manga4Life(context: MangaLoaderContext) : PagedMangaParser(contex
|
|
|
|
|
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"
|
|
|
|
|
return when {
|
|
|
|
|
b == 0 && a.isNotEmpty() -> a
|
|
|
|
|
b == 0 && a.isEmpty() -> "0"
|
|
|
|
|
else -> "$a.$b"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -246,14 +236,15 @@ internal class Manga4Life(context: MangaLoaderContext) : PagedMangaParser(contex
|
|
|
|
|
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(";"),
|
|
|
|
|
doc.selectFirstOrThrow("script:containsData(MainFunction)").data()
|
|
|
|
|
.substringAfter("vm.CurChapter = ")
|
|
|
|
|
.substringBefore(';'),
|
|
|
|
|
)
|
|
|
|
|
val pageTotal = curChapter.getString("Page")!!.toInt()
|
|
|
|
|
val host = "https://" +
|
|
|
|
|
script
|
|
|
|
|
.substringAfter("vm.CurPathName = \"", "")
|
|
|
|
|
.substringBefore("\"")
|
|
|
|
|
.substringBefore('"')
|
|
|
|
|
.also {
|
|
|
|
|
if (it.isEmpty()) {
|
|
|
|
|
throw Exception("Manga4Life is overloaded and blocking Tachiyomi right now. Wait for unblock.")
|
|
|
|
|
|