diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ReaperComics.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ReaperComics.kt deleted file mode 100644 index 63cf8ea4..00000000 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/en/ReaperComics.kt +++ /dev/null @@ -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 = 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 { - 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 { - 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 { - 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>) { - 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 { - val doc = webClient.httpGet(chapter.url.toAbsoluteUrl(domain)).parseHtml() - val processedUrls = mutableSetOf() - - 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 - } - } - } - } -} - diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/HeanCms.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/HeanCms.kt index 4968b05b..5d2c21b3 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/HeanCms.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/HeanCms.kt @@ -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 { 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()) + } + + protected open val cdn = "api.$domain/" - 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") + private fun parseMangaList(response: JSONObject): List { + 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 { 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() } } diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/en/OmegaScans.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/en/OmegaScans.kt index 2869155c..82ef8254 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/en/OmegaScans.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/en/OmegaScans.kt @@ -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 { - 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 { - 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") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/en/ReaperComics.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/en/ReaperComics.kt new file mode 100644 index 00000000..5e3d1416 --- /dev/null +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/en/ReaperComics.kt @@ -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" +} diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/es/YugenMangasEs.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/es/YugenMangasEs.kt index e3d48ef9..6e68e7c3 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/es/YugenMangasEs.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/es/YugenMangasEs.kt @@ -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 { - 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") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/fr/PerfScan.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/fr/PerfScan.kt index d09c8349..db5adfc6 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/fr/PerfScan.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/fr/PerfScan.kt @@ -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 { - 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 { - 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") diff --git a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/pt/ModeScanlator.kt b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/pt/ModeScanlator.kt index 69c46421..9186d5d4 100644 --- a/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/pt/ModeScanlator.kt +++ b/src/main/kotlin/org/koitharu/kotatsu/parsers/site/heancms/pt/ModeScanlator.kt @@ -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 { - 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 { - 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" } +