Move ReaperComics to HeanCms
Rework heancms based on @NagaYZ's code
This commit is contained in:
@@ -1,263 +0,0 @@
|
||||
package org.koitharu.kotatsu.parsers.site.en
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.Headers
|
||||
import okhttp3.Request
|
||||
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.util.*
|
||||
import org.koitharu.kotatsu.parsers.util.json.*
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
private const val MAX_RETRY_COUNT = 5
|
||||
|
||||
@MangaSourceParser("REAPERCOMICS", "ReaperComics", "en")
|
||||
internal class ReaperComics(context: MangaLoaderContext) :
|
||||
PagedMangaParser(context, MangaParserSource.REAPERCOMICS, pageSize = 20) {
|
||||
|
||||
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
|
||||
SortOrder.UPDATED,
|
||||
SortOrder.ALPHABETICAL,
|
||||
SortOrder.POPULARITY,
|
||||
SortOrder.NEWEST,
|
||||
SortOrder.ALPHABETICAL_DESC,
|
||||
)
|
||||
|
||||
override val configKeyDomain = ConfigKey.Domain("reaperscans.com")
|
||||
|
||||
private val userAgentKey = ConfigKey.UserAgent(context.getDefaultUserAgent())
|
||||
|
||||
private val baseHeaders: Headers
|
||||
get() = Headers.Builder().add("User-Agent", config[userAgentKey]).build()
|
||||
|
||||
override val headers
|
||||
get() = getApiHeaders()
|
||||
|
||||
private fun getApiHeaders(): Headers {
|
||||
val userCookie = context.cookieJar.getCookies(domain).find {
|
||||
it.name == "user"
|
||||
} ?: return baseHeaders
|
||||
val jo = JSONObject(userCookie.value.urlDecode())
|
||||
val accessToken = jo.getStringOrNull("access_token") ?: return baseHeaders
|
||||
return baseHeaders.newBuilder().add("authorization", "bearer $accessToken").build()
|
||||
}
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
if (page > 1) return emptyList()
|
||||
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
append("api.$domain")
|
||||
append("/query?page=$page&perPage=9999&series_type=Comic")
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append("&query_string=")
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
append("&orderBy=")
|
||||
val order = when (filter.sortOrder) {
|
||||
SortOrder.UPDATED -> "updated_at"
|
||||
SortOrder.POPULARITY -> "total_views"
|
||||
SortOrder.NEWEST -> "created_at"
|
||||
SortOrder.ALPHABETICAL -> "title"
|
||||
SortOrder.ALPHABETICAL_DESC -> "title"
|
||||
else -> "updated_at"
|
||||
}
|
||||
append(order)
|
||||
val sortOrder = if (filter.sortOrder == SortOrder.ALPHABETICAL_DESC) "desc" else "asc"
|
||||
append("&order=$sortOrder")
|
||||
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&status=")
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "Ongoing"
|
||||
MangaState.FINISHED -> "Completed"
|
||||
MangaState.ABANDONED -> "Dropped"
|
||||
MangaState.PAUSED -> "Hiatus"
|
||||
else -> "All"
|
||||
},
|
||||
)
|
||||
}
|
||||
if (filter.tags.isNotEmpty()) {
|
||||
append("&tags_ids=")
|
||||
append(filter.tags.joinToString(separator = "%") { it.key })
|
||||
}
|
||||
}
|
||||
|
||||
null -> {
|
||||
append("&orderBy=updated_at")
|
||||
append("&order=asc")
|
||||
append("&adult=true")
|
||||
append("&status=All")
|
||||
}
|
||||
}
|
||||
}
|
||||
return parseMangaList(webClient.httpGet(url).parseJson())
|
||||
}
|
||||
|
||||
|
||||
private fun parseMangaList(response: JSONObject): List<Manga> {
|
||||
return response.getJSONArray("data").mapJSON { it ->
|
||||
val id = it.getLong("id")
|
||||
val url = "/comic/${it.getString("series_slug")}"
|
||||
val title = it.getString("title")
|
||||
val thumbnailPath = it.getString("thumbnail")
|
||||
Manga(
|
||||
id = id,
|
||||
url = url,
|
||||
title = title,
|
||||
altTitle = it.getString("alternative_names").takeIf { it.isNotBlank() },
|
||||
publicUrl = url.toAbsoluteUrl(domain),
|
||||
description = it.getString("description"),
|
||||
rating = it.getFloatOrDefault("rating", RATING_UNKNOWN) / 5f,
|
||||
isNsfw = isNsfwSource,
|
||||
coverUrl = "https://media.reaperscans.com/file/4SRBHm//$thumbnailPath",
|
||||
tags = emptySet(),
|
||||
state = when (it.getString("status")) {
|
||||
"Ongoing" -> MangaState.ONGOING
|
||||
"Completed" -> MangaState.FINISHED
|
||||
"Dropped" -> MangaState.ABANDONED
|
||||
"Hiatus" -> MangaState.PAUSED
|
||||
else -> null
|
||||
},
|
||||
author = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
val doc = webClient.httpGet("https://$domain/comics").parseHtml()
|
||||
val scriptContent = doc.select("script").find {
|
||||
it.data().contains("tags")
|
||||
}?.data()
|
||||
|
||||
if (scriptContent != null) {
|
||||
val jsonString = scriptContent.substringAfter("push(").substringBeforeLast(")")
|
||||
val jsonArray = JSONArray(jsonString)
|
||||
val childrenArray = jsonArray.getString(1)
|
||||
val tagsString = childrenArray.substringAfter("tags:[").substringBeforeLast("]")
|
||||
val tagObjects = tagsString.split("},{")
|
||||
|
||||
return tagObjects.mapNotNullTo(mutableSetOf()) { tagString ->
|
||||
|
||||
val id = tagString.substringAfter("\"id\":").substringBefore(",")
|
||||
val name = tagString.substringAfter("\"name\":\"").substringBefore("\"")
|
||||
if (id.isNotEmpty() && name.isNotEmpty()) {
|
||||
MangaTag(
|
||||
key = id,
|
||||
title = name.toTitleCase(sourceLocale),
|
||||
source = source,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
return emptySet()
|
||||
}
|
||||
|
||||
override fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
|
||||
super.onCreateConfig(keys)
|
||||
keys.add(userAgentKey)
|
||||
}
|
||||
|
||||
private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", sourceLocale)
|
||||
|
||||
override suspend fun getDetails(manga: Manga): Manga {
|
||||
val seriesid = manga.id
|
||||
val url = "https://api.$domain/chapter/query?page=1&perPage=9999&series_id=$seriesid"
|
||||
val response = makeRequest(url)
|
||||
val data = response.getJSONArray("data")
|
||||
return manga.copy(
|
||||
chapters = data.mapJSONIndexed { index, it ->
|
||||
val chapterUrl =
|
||||
"/series/${it.getJSONObject("series").getString("series_slug")}/${it.getString("chapter_slug")}"
|
||||
MangaChapter(
|
||||
id = it.getLong("id"),
|
||||
name = it.getString("chapter_name"),
|
||||
number = (data.length() - index).toFloat(),
|
||||
volume = 0,
|
||||
url = chapterUrl,
|
||||
scanlator = null,
|
||||
uploadDate = parseChapterDate(dateFormat, it.getString("created_at")),
|
||||
branch = null,
|
||||
source = source,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun parseChapterDate(dateFormat: DateFormat, date: String?): Long {
|
||||
return try {
|
||||
dateFormat.tryParse(date)
|
||||
} catch (e: Exception) {
|
||||
0L
|
||||
}
|
||||
}
|
||||
|
||||
private val pageSelector = "div#content div.container img"
|
||||
|
||||
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
|
||||
val doc = webClient.httpGet(chapter.url.toAbsoluteUrl(domain)).parseHtml()
|
||||
val processedUrls = mutableSetOf<String>()
|
||||
|
||||
return doc.select(pageSelector).mapNotNull { img ->
|
||||
val url = img.attr("data-cfsrc").takeIf { it.isNotBlank() }
|
||||
?: img.attr("src").takeIf { it.isNotBlank() }
|
||||
?: img.selectFirst("noscript img")?.attr("src")
|
||||
?: return@mapNotNull null
|
||||
|
||||
val relativeUrl = url.toRelativeUrl(domain)
|
||||
|
||||
if (relativeUrl !in processedUrls) {
|
||||
processedUrls.add(relativeUrl)
|
||||
MangaPage(
|
||||
id = generateUid(relativeUrl),
|
||||
url = relativeUrl,
|
||||
preview = null,
|
||||
source = source,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun makeRequest(url: String): JSONObject {
|
||||
var retryCount = 0
|
||||
val backoffDelay = 2000L // Initial delay (milliseconds)
|
||||
val request = Request.Builder().url(url).headers(headers).build()
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
val response = context.httpClient.newCall(request).execute().parseJson()
|
||||
return response
|
||||
|
||||
} catch (e: Exception) {
|
||||
// Log or handle the exception as needed
|
||||
if (++retryCount <= MAX_RETRY_COUNT) {
|
||||
withContext(Dispatchers.Default) {
|
||||
delay(backoffDelay)
|
||||
}
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
package org.koitharu.kotatsu.parsers.site.heancms
|
||||
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.PagedMangaParser
|
||||
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import org.koitharu.kotatsu.parsers.util.json.getFloatOrDefault
|
||||
import org.koitharu.kotatsu.parsers.util.json.mapJSON
|
||||
import org.koitharu.kotatsu.parsers.util.json.mapJSONIndexed
|
||||
import org.koitharu.kotatsu.parsers.util.json.unescapeJson
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@@ -41,6 +46,8 @@ internal abstract class HeanCms(
|
||||
protected open val apiPath
|
||||
get() = getDomain("api")
|
||||
|
||||
protected open val paramsUpdated = "latest"
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://")
|
||||
@@ -54,7 +61,7 @@ internal abstract class HeanCms(
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&series_status=")
|
||||
append("&status=")
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "Ongoing"
|
||||
@@ -64,18 +71,19 @@ internal abstract class HeanCms(
|
||||
else -> ""
|
||||
},
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
append("&orderBy=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("total_views&order=desc")
|
||||
SortOrder.UPDATED -> append("latest&order=desc")
|
||||
SortOrder.UPDATED -> append("$paramsUpdated&order=desc")
|
||||
SortOrder.NEWEST -> append("created_at&order=desc")
|
||||
SortOrder.ALPHABETICAL -> append("title&order=desc")
|
||||
SortOrder.ALPHABETICAL_DESC -> append("title&order=asc")
|
||||
SortOrder.ALPHABETICAL -> append("title&order=asc")
|
||||
SortOrder.ALPHABETICAL_DESC -> append("title&order=desc")
|
||||
else -> append("latest&order=desc")
|
||||
}
|
||||
append("&series_type=Comic&perPage=12")
|
||||
|
||||
append("&series_type=Comic&perPage=20")
|
||||
append("&tags_ids=")
|
||||
append("[".urlEncoded())
|
||||
append(filter.tags.joinToString(",") { it.key })
|
||||
@@ -83,32 +91,39 @@ internal abstract class HeanCms(
|
||||
|
||||
}
|
||||
|
||||
null -> {}
|
||||
null -> append("status=All&orderBy=$paramsUpdated&order=desc&series_type=Comic&perPage=20")
|
||||
}
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
val json = webClient.httpGet(url).parseJson()
|
||||
return parseMangaList(webClient.httpGet(url).parseJson())
|
||||
}
|
||||
|
||||
return json.getJSONArray("data").mapJSON { j ->
|
||||
val slug = j.getString("series_slug")
|
||||
val urlManga = "https://$domain/$pathManga/$slug"
|
||||
val cover = if (j.getString("thumbnail").contains('/')) {
|
||||
j.getString("thumbnail")
|
||||
protected open val cdn = "api.$domain/"
|
||||
|
||||
private fun parseMangaList(response: JSONObject): List<Manga> {
|
||||
return response.getJSONArray("data").mapJSON { it ->
|
||||
val id = it.getLong("id")
|
||||
val url = "/comic/${it.getString("series_slug")}"
|
||||
val title = it.getString("title")
|
||||
val cover = if (it.getString("thumbnail").startsWith("https://")) {
|
||||
it.getString("thumbnail")
|
||||
} else {
|
||||
"https://api.$domain/${j.getString("thumbnail")}"
|
||||
"https://$cdn${it.getString("thumbnail")}"
|
||||
}
|
||||
|
||||
Manga(
|
||||
id = generateUid(urlManga),
|
||||
title = j.getString("title"),
|
||||
altTitle = null,
|
||||
url = urlManga.toRelativeUrl(domain),
|
||||
publicUrl = urlManga,
|
||||
rating = RATING_UNKNOWN,
|
||||
isNsfw = false,
|
||||
id = id,
|
||||
url = url,
|
||||
title = title,
|
||||
altTitle = it.getString("alternative_names").takeIf { it.isNotBlank() },
|
||||
publicUrl = url.replace("/comic/", "/series/").toAbsoluteUrl(domain),
|
||||
description = it.getString("description"),
|
||||
rating = it.getFloatOrDefault("rating", RATING_UNKNOWN) / 5f,
|
||||
isNsfw = isNsfwSource,
|
||||
coverUrl = cover,
|
||||
tags = setOf(),
|
||||
state = when (j.getString("status")) {
|
||||
tags = emptySet(),
|
||||
state = when (it.getString("status")) {
|
||||
"Ongoing" -> MangaState.ONGOING
|
||||
"Completed" -> MangaState.FINISHED
|
||||
"Dropped" -> MangaState.ABANDONED
|
||||
@@ -119,41 +134,28 @@ internal abstract class HeanCms(
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected open val datePattern = "yyyy-MM-dd"
|
||||
|
||||
override suspend fun getDetails(manga: Manga): Manga {
|
||||
val root = webClient.httpGet(manga.url.toAbsoluteUrl(domain)).parseHtml()
|
||||
val seriesId = manga.id
|
||||
val url = "https://$apiPath/chapter/query?page=1&perPage=9999&series_id=$seriesId"
|
||||
val response = webClient.httpGet(url).parseJson()
|
||||
val data = response.getJSONArray("data")
|
||||
val dateFormat = SimpleDateFormat(datePattern, Locale.ENGLISH)
|
||||
|
||||
val slug = manga.url.substringAfterLast('/')
|
||||
val chapter = root.selectFirstOrThrow("script:containsData(chapter_slug)").data()
|
||||
.replace("\\", "")
|
||||
.substringAfter("\"seasons\":")
|
||||
.substringBefore("}]}],\"children\"")
|
||||
.split("chapter_name")
|
||||
.drop(1)
|
||||
|
||||
return manga.copy(
|
||||
altTitle = root.selectFirst("p.text-center.text-gray-400")?.text(),
|
||||
tags = emptySet(),
|
||||
author = root.select("div.flex.flex-col.gap-y-2 p:contains(Autor:) strong").text(),
|
||||
description = root.selectFirst("h5:contains(Desc) + .bg-gray-800")?.html(),
|
||||
chapters = chapter.mapChapters(reversed = true) { i, it ->
|
||||
val slugChapter = it.substringAfter("chapter_slug\":\"").substringBefore("\",\"")
|
||||
val url = "https://$domain/$pathManga/$slug/$slugChapter"
|
||||
val date = it.substringAfter("created_at\":\"").substringBefore("\",\"").substringBefore("T")
|
||||
val name = slugChapter.replace("-", " ")
|
||||
chapters = data.mapJSONIndexed { index, it ->
|
||||
val chapterUrl =
|
||||
"/series/${it.getJSONObject("series").getString("series_slug")}/${it.getString("chapter_slug")}"
|
||||
MangaChapter(
|
||||
id = generateUid(url),
|
||||
name = name,
|
||||
number = i + 1f,
|
||||
id = it.getLong("id"),
|
||||
name = it.getString("chapter_name"),
|
||||
number = (data.length() - index).toFloat(),
|
||||
volume = 0,
|
||||
url = url,
|
||||
url = chapterUrl,
|
||||
scanlator = null,
|
||||
uploadDate = dateFormat.tryParse(date),
|
||||
uploadDate = dateFormat.tryParse(it.getString("created_at").substringBefore("T")),
|
||||
branch = null,
|
||||
source = source,
|
||||
)
|
||||
@@ -177,19 +179,16 @@ internal abstract class HeanCms(
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
val doc = webClient.httpGet("https://$domain/comics").parseHtml()
|
||||
|
||||
val tags = doc.selectFirstOrThrow("script:containsData(Genres)").data()
|
||||
.replace("\\", "")
|
||||
.substringAfterLast("\"Genres\"")
|
||||
.split("\",{\"")
|
||||
.drop(1)
|
||||
|
||||
return tags.mapNotNullToSet {
|
||||
val regex = Regex("\"tags\\\\.*?(\\[.+?])")
|
||||
val tags = doc.select("script").firstNotNullOf { script ->
|
||||
regex.find(script.html())?.groupValues?.getOrNull(1)
|
||||
}.unescapeJson()
|
||||
return JSONArray(tags).mapJSON {
|
||||
MangaTag(
|
||||
key = it.substringAfter("id\":").substringBefore(",\""),
|
||||
title = it.substringAfter("name\":\"").substringBefore("\"}]").toTitleCase(sourceLocale),
|
||||
key = it.getInt("id").toString(),
|
||||
title = it.getString("name").toTitleCase(sourceLocale),
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}.toSet()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,149 +1,10 @@
|
||||
package org.koitharu.kotatsu.parsers.site.heancms.en
|
||||
|
||||
import org.json.JSONArray
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.site.heancms.HeanCms
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import org.koitharu.kotatsu.parsers.util.json.getFloatOrDefault
|
||||
import org.koitharu.kotatsu.parsers.util.json.getStringOrNull
|
||||
import org.koitharu.kotatsu.parsers.util.json.mapJSON
|
||||
import org.koitharu.kotatsu.parsers.util.json.unescapeJson
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@MangaSourceParser("OMEGASCANS", "OmegaScans", "en", ContentType.HENTAI)
|
||||
internal class OmegaScans(context: MangaLoaderContext) :
|
||||
HeanCms(context, MangaParserSource.OMEGASCANS, "omegascans.org") {
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://api.")
|
||||
append(domain)
|
||||
append("/query?adult=true&query_string=")
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&series_status=")
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "Ongoing"
|
||||
MangaState.FINISHED -> "Completed"
|
||||
MangaState.ABANDONED -> "Dropped"
|
||||
MangaState.PAUSED -> "Hiatus"
|
||||
else -> ""
|
||||
},
|
||||
)
|
||||
|
||||
}
|
||||
append("&orderBy=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("total_views&order=desc")
|
||||
SortOrder.UPDATED -> append("latest&order=desc")
|
||||
SortOrder.NEWEST -> append("created_at&order=desc")
|
||||
SortOrder.ALPHABETICAL -> append("title&order=asc")
|
||||
SortOrder.ALPHABETICAL_DESC -> append("title&order=desc")
|
||||
else -> append("latest&order=desc")
|
||||
}
|
||||
append("&series_type=All&perPage=$pageSize")
|
||||
append("&tags_ids=")
|
||||
append("[".urlEncoded())
|
||||
append(filter.tags.joinToString(",") { it.key })
|
||||
append("]".urlEncoded())
|
||||
|
||||
}
|
||||
|
||||
null -> {}
|
||||
}
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
val json = webClient.httpGet(url).parseJson()
|
||||
return json.getJSONArray("data").mapJSON { j ->
|
||||
val slug = j.getString("series_slug")
|
||||
val urlManga = "https://$domain/$pathManga/$slug"
|
||||
val cover = if (j.getString("thumbnail").contains('/')) {
|
||||
j.getString("thumbnail")
|
||||
} else {
|
||||
"https://api.$domain/${j.getString("thumbnail")}"
|
||||
}
|
||||
Manga(
|
||||
id = j.getLong("id"),
|
||||
title = j.getString("title"),
|
||||
altTitle = null,
|
||||
url = urlManga.toRelativeUrl(domain),
|
||||
publicUrl = urlManga,
|
||||
rating = j.getFloatOrDefault("rating", RATING_UNKNOWN) / 5f,
|
||||
isNsfw = true,
|
||||
coverUrl = cover,
|
||||
tags = setOf(),
|
||||
state = when (j.getString("status")) {
|
||||
"Ongoing" -> MangaState.ONGOING
|
||||
"Completed" -> MangaState.FINISHED
|
||||
"Dropped" -> MangaState.ABANDONED
|
||||
"Hiatus" -> MangaState.PAUSED
|
||||
else -> null
|
||||
},
|
||||
author = j.getStringOrNull("author"),
|
||||
source = source,
|
||||
description = j.getString("description"),
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override suspend fun getDetails(manga: Manga): Manga {
|
||||
val url = buildString {
|
||||
append("https://api.")
|
||||
append(domain)
|
||||
append("/chapter/query?perPage=9999&series_id=" + manga.id)
|
||||
}
|
||||
val json = webClient.httpGet(url).parseJson()
|
||||
val dateFormat = SimpleDateFormat(datePattern, Locale.ENGLISH)
|
||||
|
||||
val chaptersJsonArray = json.getJSONArray("data")
|
||||
var totalChapters = json.getJSONObject("meta").getInt("total").toFloat()
|
||||
val chapters = chaptersJsonArray.mapJSON { j ->
|
||||
val slug = j.getJSONObject("series").getString("series_slug")
|
||||
val chapterUrl = "https://$domain/$pathManga/$slug/${j.getString("chapter_slug")}"
|
||||
val date = j.getString("created_at").substringBeforeLast("T")
|
||||
MangaChapter(
|
||||
id = j.getLong("id"),
|
||||
url = chapterUrl,
|
||||
name = j.getString("chapter_name"),
|
||||
number = totalChapters--,
|
||||
volume = 0,
|
||||
branch = null,
|
||||
uploadDate = dateFormat.tryParse(date),
|
||||
scanlator = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
|
||||
return manga.copy(
|
||||
chapters = chapters.reversed(),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
val doc = webClient.httpGet("https://$domain/comics").parseHtml()
|
||||
|
||||
val regex = Regex("\"tags\\\\.*?(\\[.+?])")
|
||||
val tags = doc.select("script").firstNotNullOf { script ->
|
||||
regex.find(script.html())?.groupValues?.getOrNull(1)
|
||||
}.unescapeJson()
|
||||
return JSONArray(tags).mapJSON {
|
||||
MangaTag(
|
||||
key = it.getInt("id").toString(),
|
||||
title = it.getString("name").toTitleCase(sourceLocale),
|
||||
source = source,
|
||||
)
|
||||
}.toSet()
|
||||
}
|
||||
|
||||
}
|
||||
HeanCms(context, MangaParserSource.OMEGASCANS, "omegascans.org")
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.koitharu.kotatsu.parsers.site.heancms.en
|
||||
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.site.heancms.HeanCms
|
||||
|
||||
@MangaSourceParser("REAPERCOMICS", "ReaperComics", "en")
|
||||
internal class ReaperComics(context: MangaLoaderContext) :
|
||||
HeanCms(context, MangaParserSource.REAPERCOMICS, "reaperscans.com") {
|
||||
override val cdn = "media.reaperscans.com/file/4SRBHm//"
|
||||
override val paramsUpdated = "updated_at"
|
||||
}
|
||||
@@ -5,94 +5,8 @@ import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.site.heancms.HeanCms
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import org.koitharu.kotatsu.parsers.util.json.mapJSON
|
||||
|
||||
@Broken // Not dead but changed template and url visualikigai.com
|
||||
@MangaSourceParser("YUGEN_MANGAS_ES", "YugenMangas.lat", "es", ContentType.HENTAI)
|
||||
internal class YugenMangasEs(context: MangaLoaderContext) :
|
||||
HeanCms(context, MangaParserSource.YUGEN_MANGAS_ES, "yugenmangas.lat") {
|
||||
|
||||
private val domainAlt = "yugenmangas.net"
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://api.")
|
||||
append(domainAlt)
|
||||
append("/query?query_string=")
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&series_status=")
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "Ongoing"
|
||||
MangaState.FINISHED -> "Completed"
|
||||
MangaState.ABANDONED -> "Dropped"
|
||||
MangaState.PAUSED -> "Hiatus"
|
||||
else -> ""
|
||||
},
|
||||
)
|
||||
|
||||
}
|
||||
append("&orderBy=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("total_views&order=desc")
|
||||
SortOrder.UPDATED -> append("latest&order=desc")
|
||||
SortOrder.NEWEST -> append("created_at&order=desc")
|
||||
SortOrder.ALPHABETICAL -> append("title&order=asc")
|
||||
SortOrder.ALPHABETICAL_DESC -> append("title&order=desc")
|
||||
else -> append("latest&order=desc")
|
||||
}
|
||||
append("&series_type=Comic&perPage=12")
|
||||
append("&tags_ids=")
|
||||
append("[".urlEncoded())
|
||||
append(filter.tags.joinToString(",") { it.key })
|
||||
append("]".urlEncoded())
|
||||
}
|
||||
|
||||
null -> {}
|
||||
}
|
||||
|
||||
append("&page=")
|
||||
append(page.toString())
|
||||
}
|
||||
|
||||
val json = webClient.httpGet(url).parseJson()
|
||||
|
||||
return json.getJSONArray("data").mapJSON { j ->
|
||||
val slug = j.getString("series_slug")
|
||||
val urlManga = "https://$domain/series/$slug"
|
||||
val cover = if (j.getString("thumbnail").contains('/')) {
|
||||
j.getString("thumbnail")
|
||||
} else {
|
||||
"https://api.$domainAlt/${j.getString("thumbnail")}"
|
||||
}
|
||||
Manga(
|
||||
id = generateUid(urlManga),
|
||||
title = j.getString("title"),
|
||||
altTitle = null,
|
||||
url = urlManga,
|
||||
publicUrl = urlManga,
|
||||
rating = RATING_UNKNOWN,
|
||||
isNsfw = false,
|
||||
coverUrl = cover,
|
||||
tags = setOf(),
|
||||
state = when (j.getString("status")) {
|
||||
"Ongoing" -> MangaState.ONGOING
|
||||
"Completed" -> MangaState.FINISHED
|
||||
"Dropped" -> MangaState.ABANDONED
|
||||
"Hiatus" -> MangaState.PAUSED
|
||||
else -> null
|
||||
},
|
||||
author = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
HeanCms(context, MangaParserSource.YUGEN_MANGAS_ES, "yugenmangas.lat")
|
||||
|
||||
@@ -1,148 +1,11 @@
|
||||
package org.koitharu.kotatsu.parsers.site.heancms.fr
|
||||
|
||||
import org.json.JSONArray
|
||||
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.site.heancms.HeanCms
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import org.koitharu.kotatsu.parsers.util.json.*
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@MangaSourceParser("PERF_SCAN", "PerfScan", "fr")
|
||||
internal class PerfScan(context: MangaLoaderContext) :
|
||||
HeanCms(context, MangaParserSource.PERF_SCAN, "perf-scan.fr") {
|
||||
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://api.")
|
||||
append(domain)
|
||||
append("/query?adult=true&query_string=")
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&status=")
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "Ongoing"
|
||||
MangaState.FINISHED -> "Completed"
|
||||
MangaState.ABANDONED -> "Dropped"
|
||||
MangaState.PAUSED -> "Hiatus"
|
||||
else -> ""
|
||||
},
|
||||
)
|
||||
|
||||
}
|
||||
append("&orderBy=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("total_views&order=desc")
|
||||
SortOrder.UPDATED -> append("latest&order=desc")
|
||||
SortOrder.NEWEST -> append("created_at&order=desc")
|
||||
SortOrder.ALPHABETICAL -> append("title&order=asc")
|
||||
SortOrder.ALPHABETICAL_DESC -> append("title&order=desc")
|
||||
else -> append("latest&order=desc")
|
||||
}
|
||||
append("&series_type=All&perPage=")
|
||||
append(pageSize)
|
||||
append("&tags_ids=")
|
||||
append("[".urlEncoded())
|
||||
filter.tags.joinTo(this, ",") { it.key }
|
||||
append("]".urlEncoded())
|
||||
|
||||
}
|
||||
|
||||
null -> {}
|
||||
}
|
||||
append("&page=")
|
||||
append(page)
|
||||
}
|
||||
val json = webClient.httpGet(url).parseJson()
|
||||
return json.getJSONArray("data").mapJSON { j ->
|
||||
val slug = j.getString("series_slug")
|
||||
val urlManga = "https://$domain/$pathManga/$slug"
|
||||
val cover = if (j.getString("thumbnail").contains('/')) {
|
||||
j.getString("thumbnail")
|
||||
} else {
|
||||
"https://api.$domain/${j.getString("thumbnail")}"
|
||||
}
|
||||
Manga(
|
||||
id = j.getLong("id"),
|
||||
title = j.getString("title"),
|
||||
altTitle = null,
|
||||
url = urlManga.toRelativeUrl(domain),
|
||||
publicUrl = urlManga,
|
||||
rating = j.getFloatOrDefault("rating", RATING_UNKNOWN) / 5f,
|
||||
isNsfw = isNsfwSource,
|
||||
coverUrl = cover,
|
||||
tags = setOf(),
|
||||
state = when (j.getStringOrNull("status")) {
|
||||
"Ongoing" -> MangaState.ONGOING
|
||||
"Completed" -> MangaState.FINISHED
|
||||
"Dropped" -> MangaState.ABANDONED
|
||||
"Hiatus" -> MangaState.PAUSED
|
||||
else -> null
|
||||
},
|
||||
author = j.getStringOrNull("author"),
|
||||
source = source,
|
||||
description = j.getString("description"),
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override suspend fun getDetails(manga: Manga): Manga {
|
||||
val url = buildString {
|
||||
append("https://api.")
|
||||
append(domain)
|
||||
append("/chapter/query?perPage=9999&series_id=")
|
||||
append(manga.id)
|
||||
}
|
||||
val json = webClient.httpGet(url).parseJson()
|
||||
val dateFormat = SimpleDateFormat(datePattern, Locale.ENGLISH)
|
||||
|
||||
val chaptersJsonArray = json.getJSONArray("data")
|
||||
var totalChapters = json.getJSONObject("meta").getInt("total").toFloat()
|
||||
val chapters = chaptersJsonArray.mapJSON { j ->
|
||||
val slug = j.getJSONObject("series").getString("series_slug")
|
||||
val chapterUrl = "https://$domain/$pathManga/$slug/${j.getString("chapter_slug")}"
|
||||
val date = j.getString("created_at").substringBeforeLast("T")
|
||||
MangaChapter(
|
||||
id = j.getLong("id"),
|
||||
url = chapterUrl,
|
||||
name = j.getString("chapter_name"),
|
||||
number = totalChapters--,
|
||||
volume = 0,
|
||||
branch = null,
|
||||
uploadDate = dateFormat.tryParse(date),
|
||||
scanlator = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
|
||||
return manga.copy(
|
||||
chapters = chapters.reversed(),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
val doc = webClient.httpGet("https://$domain/comics").parseHtml()
|
||||
|
||||
val regex = Regex("\"tags\\\\.*?(\\[.+?])")
|
||||
val tags = doc.select("script").firstNotNullOf { script ->
|
||||
regex.find(script.html())?.groupValues?.getOrNull(1)
|
||||
}.unescapeJson()
|
||||
return JSONArray(tags).mapJSONToSet {
|
||||
MangaTag(
|
||||
key = it.getInt("id").toString(),
|
||||
title = it.getString("name").toTitleCase(sourceLocale),
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
HeanCms(context, MangaParserSource.PERF_SCAN, "perf-scan.fr")
|
||||
|
||||
@@ -1,150 +1,13 @@
|
||||
package org.koitharu.kotatsu.parsers.site.heancms.pt
|
||||
|
||||
import org.json.JSONArray
|
||||
import org.koitharu.kotatsu.parsers.MangaLoaderContext
|
||||
import org.koitharu.kotatsu.parsers.MangaSourceParser
|
||||
import org.koitharu.kotatsu.parsers.model.*
|
||||
import org.koitharu.kotatsu.parsers.site.heancms.HeanCms
|
||||
import org.koitharu.kotatsu.parsers.util.*
|
||||
import org.koitharu.kotatsu.parsers.util.json.*
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@MangaSourceParser("MODESCANLATOR", "ModeScanlator", "pt")
|
||||
internal class ModeScanlator(
|
||||
context: MangaLoaderContext,
|
||||
) : HeanCms(context, MangaParserSource.MODESCANLATOR, "site.modescanlator.net") {
|
||||
|
||||
private val domainNoSite = domain.removePrefix("site.")
|
||||
override suspend fun getListPage(page: Int, filter: MangaListFilter?): List<Manga> {
|
||||
val url = buildString {
|
||||
append("https://api.")
|
||||
append(domainNoSite)
|
||||
append("/query?adult=true&query_string=")
|
||||
when (filter) {
|
||||
is MangaListFilter.Search -> {
|
||||
append(filter.query.urlEncoded())
|
||||
}
|
||||
|
||||
is MangaListFilter.Advanced -> {
|
||||
|
||||
filter.states.oneOrThrowIfMany()?.let {
|
||||
append("&series_status=")
|
||||
append(
|
||||
when (it) {
|
||||
MangaState.ONGOING -> "Ongoing"
|
||||
MangaState.FINISHED -> "Completed"
|
||||
MangaState.ABANDONED -> "Dropped"
|
||||
MangaState.PAUSED -> "Hiatus"
|
||||
else -> ""
|
||||
},
|
||||
)
|
||||
|
||||
}
|
||||
append("&orderBy=")
|
||||
when (filter.sortOrder) {
|
||||
SortOrder.POPULARITY -> append("total_views&order=desc")
|
||||
SortOrder.UPDATED -> append("latest&order=desc")
|
||||
SortOrder.NEWEST -> append("created_at&order=desc")
|
||||
SortOrder.ALPHABETICAL -> append("title&order=asc")
|
||||
SortOrder.ALPHABETICAL_DESC -> append("title&order=desc")
|
||||
else -> append("latest&order=desc")
|
||||
}
|
||||
append("&series_type=All&perPage=")
|
||||
append(pageSize)
|
||||
append("&tags_ids=")
|
||||
append("[".urlEncoded())
|
||||
filter.tags.joinTo(this, ",") { it.key }
|
||||
append("]".urlEncoded())
|
||||
|
||||
}
|
||||
|
||||
null -> {}
|
||||
}
|
||||
append("&page=")
|
||||
append(page)
|
||||
}
|
||||
val json = webClient.httpGet(url).parseJson()
|
||||
return json.getJSONArray("data").mapJSON { j ->
|
||||
val slug = j.getString("series_slug")
|
||||
val urlManga = "https://$domain/$pathManga/$slug"
|
||||
val cover = if (j.getString("thumbnail").contains('/')) {
|
||||
j.getString("thumbnail")
|
||||
} else {
|
||||
"https://api.$domainNoSite/${j.getString("thumbnail")}"
|
||||
}
|
||||
Manga(
|
||||
id = j.getLong("id"),
|
||||
title = j.getString("title"),
|
||||
altTitle = null,
|
||||
url = urlManga.toRelativeUrl(domain),
|
||||
publicUrl = urlManga,
|
||||
rating = j.getFloatOrDefault("rating", RATING_UNKNOWN) / 5f,
|
||||
isNsfw = isNsfwSource,
|
||||
coverUrl = cover,
|
||||
tags = setOf(),
|
||||
state = when (j.getStringOrNull("status")) {
|
||||
"Ongoing" -> MangaState.ONGOING
|
||||
"Completed" -> MangaState.FINISHED
|
||||
"Dropped" -> MangaState.ABANDONED
|
||||
"Hiatus" -> MangaState.PAUSED
|
||||
else -> null
|
||||
},
|
||||
author = j.getStringOrNull("author"),
|
||||
source = source,
|
||||
description = j.getString("description"),
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override suspend fun getDetails(manga: Manga): Manga {
|
||||
val url = buildString {
|
||||
append("https://api.")
|
||||
append(domainNoSite)
|
||||
append("/chapter/query?perPage=9999&series_id=")
|
||||
append(manga.id)
|
||||
}
|
||||
val json = webClient.httpGet(url).parseJson()
|
||||
val dateFormat = SimpleDateFormat(datePattern, Locale.ENGLISH)
|
||||
|
||||
val chaptersJsonArray = json.getJSONArray("data")
|
||||
var totalChapters = json.getJSONObject("meta").getInt("total").toFloat()
|
||||
val chapters = chaptersJsonArray.mapJSON { j ->
|
||||
val slug = j.getJSONObject("series").getString("series_slug")
|
||||
val chapterUrl = "https://$domain/$pathManga/$slug/${j.getString("chapter_slug")}"
|
||||
val date = j.getString("created_at").substringBeforeLast("T")
|
||||
MangaChapter(
|
||||
id = j.getLong("id"),
|
||||
url = chapterUrl,
|
||||
name = j.getString("chapter_name"),
|
||||
number = totalChapters--,
|
||||
volume = 0,
|
||||
branch = null,
|
||||
uploadDate = dateFormat.tryParse(date),
|
||||
scanlator = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
|
||||
return manga.copy(
|
||||
chapters = chapters.reversed(),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getAvailableTags(): Set<MangaTag> {
|
||||
val doc = webClient.httpGet("https://$domain/comics").parseHtml()
|
||||
|
||||
val regex = Regex("\"tags\\\\.*?(\\[.+?])")
|
||||
val tags = doc.select("script").firstNotNullOf { script ->
|
||||
regex.find(script.html())?.groupValues?.getOrNull(1)
|
||||
}.unescapeJson()
|
||||
return JSONArray(tags).mapJSONToSet {
|
||||
MangaTag(
|
||||
key = it.getInt("id").toString(),
|
||||
title = it.getString("name").toTitleCase(sourceLocale),
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
internal class ModeScanlator(context: MangaLoaderContext) :
|
||||
HeanCms(context, MangaParserSource.MODESCANLATOR, "site.modescanlator.net") {
|
||||
override val apiPath = "api.modescanlator.net"
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user