InManga: Add source (#2073)

master
Naga 9 months ago committed by GitHub
parent 8964bb1584
commit 134656b835
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -1 +1 @@
total: 1245
total: 1246

@ -0,0 +1,248 @@
package org.koitharu.kotatsu.parsers.site.es
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import org.json.JSONObject
import org.jsoup.nodes.Element
import org.koitharu.kotatsu.parsers.MangaLoaderContext
import org.koitharu.kotatsu.parsers.MangaSourceParser
import org.koitharu.kotatsu.parsers.config.ConfigKey
import org.koitharu.kotatsu.parsers.core.PagedMangaParser
import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaListFilterCapabilities
import org.koitharu.kotatsu.parsers.model.MangaListFilterOptions
import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.model.MangaState
import org.koitharu.kotatsu.parsers.model.RATING_UNKNOWN
import org.koitharu.kotatsu.parsers.model.SortOrder
import org.koitharu.kotatsu.parsers.util.json.getBooleanOrDefault
import org.koitharu.kotatsu.parsers.util.json.getStringOrNull
import org.koitharu.kotatsu.parsers.util.json.mapJSON
import org.koitharu.kotatsu.parsers.util.json.toJSONObjectOrNull
import org.koitharu.kotatsu.parsers.util.parseHtml
import org.koitharu.kotatsu.parsers.util.parseJson
import java.text.SimpleDateFormat
import java.util.EnumSet
@MangaSourceParser("INMANGA", "InManga", "es", ContentType.MANGA)
internal class InMangaParser(context: MangaLoaderContext) : PagedMangaParser(
context,
source = MangaParserSource.INMANGA,
pageSize = 10,
) {
override val configKeyDomain = ConfigKey.Domain("inmanga.com")
override val availableSortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.ALPHABETICAL,
SortOrder.RELEVANCE,
SortOrder.POPULARITY,
SortOrder.NEWEST,
SortOrder.UPDATED,
)
override val filterCapabilities: MangaListFilterCapabilities
get() = MangaListFilterCapabilities(
isSearchSupported = true,
isSearchWithFiltersSupported = false,
)
override suspend fun getFilterOptions(): MangaListFilterOptions {
return MangaListFilterOptions(
availableTags = emptySet(),
availableContentTypes = EnumSet.of(ContentType.MANGA),
)
}
private val postHeaders = Headers.Builder()
.add("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
.add("X-Requested-With", "XMLHttpRequest")
.build()
private val imageCDN = "https://pack-yak.intomanga.com/"
private val chapterDateFormat = SimpleDateFormat("yyyy-MM-dd", sourceLocale)
private fun buildFormData(
page: Int,
order: SortOrder,
query: String = "",
genres: Set<String> = emptySet(),
): Map<String, String> {
val sortValue = when (order) {
SortOrder.ALPHABETICAL -> "5" // Nombre
SortOrder.RELEVANCE -> "2" // Relevancia
SortOrder.POPULARITY -> "1" // Vistos
SortOrder.NEWEST -> "4" // Recién agregado
SortOrder.UPDATED -> "3" // Recién actualizado
else -> "3" // Default to Recién actualizado
}
val formData = mutableMapOf(
"filter[queryString]" to query,
"filter[skip]" to "${(page - 1) * 10}",
"filter[take]" to "10",
"filter[sortby]" to sortValue,
"filter[broadcastStatus]" to "0",
"filter[onlyFavorites]" to "false",
"d" to "",
)
// Add genres
if (genres.isEmpty()) {
formData["filter[generes][]"] = "-1"
} else {
genres.forEachIndexed { index, genre ->
formData["filter[generes][$index]"] = genre
}
}
return formData
}
override suspend fun getListPage(page: Int, order: SortOrder, filter: MangaListFilter): List<Manga> {
val genres = filter.tags.map { it.key }.toSet()
val formData = buildFormData(page, order, filter.query ?: "", genres)
val response = webClient.httpPost(
"https://$domain/manga/getMangasConsultResult".toHttpUrl(),
formData,
postHeaders,
)
val document = response.parseHtml()
return document.select("body > a").map { element ->
parseMangaFromElement(element)
}
}
private fun parseMangaFromElement(element: Element): Manga {
val url = element.attr("href")
val title = element.select("h4.m0").text()
val coverUrl = element.select("img").attr("abs:data-src")
return Manga(
id = url.hashCode().toLong(),
title = title,
altTitles = emptySet(),
url = url,
publicUrl = "https://$domain$url",
rating = RATING_UNKNOWN,
contentRating = null,
coverUrl = coverUrl,
tags = emptySet(),
state = null,
authors = emptySet(),
description = null,
chapters = null,
source = source,
)
}
override suspend fun getDetails(manga: Manga): Manga {
val response = webClient.httpGet(manga.publicUrl.toHttpUrl())
val document = response.parseHtml()
val infoPanel = document.select("div.col-md-3 div.panel.widget")
val contentPanel = document.select("div.col-md-9")
val coverUrl = infoPanel.select("img").attr("abs:src")
val statusText = infoPanel.select("a.list-group-item:contains(estado) span").text()
val title = contentPanel.select("h1").text()
val description = contentPanel.select("div.panel-body").text()
val chapters = getChapters(manga)
return manga.copy(
coverUrl = coverUrl,
title = title,
description = description,
state = parseStatus(statusText),
chapters = chapters,
)
}
private fun parseStatus(status: String?): MangaState? {
return when {
status == null -> null
status.contains("En emisión") -> MangaState.ONGOING
status.contains("Finalizado") -> MangaState.FINISHED
else -> null
}
}
private suspend fun getChapters(manga: Manga): List<MangaChapter> {
val mangaId = manga.url.substringAfterLast("/")
val response = webClient.httpGet(
"https://$domain/chapter/getall?mangaIdentification=$mangaId".toHttpUrl(),
)
// The server returns a JSON with data property that contains a string with the JSON,
// so is necessary to decode twice.
val json = response.parseJson()
val dataString = json.getStringOrNull("data") ?: return emptyList()
val dataJson = dataString.toJSONObjectOrNull() ?: return emptyList()
if (!dataJson.getBooleanOrDefault("success", false)) {
return emptyList()
}
val chaptersArray = dataJson.optJSONArray("result") ?: return emptyList()
return chaptersArray.mapJSON { chapterJson ->
parseChapterFromJson(chapterJson)
}.sortedBy { it.number }
}
private fun parseChapterFromJson(chapterJson: JSONObject): MangaChapter {
val identification = chapterJson.getStringOrNull("Identification") ?: ""
val friendlyChapterNumber = chapterJson.getStringOrNull("FriendlyChapterNumber") ?: ""
val number = chapterJson.optDouble("Number").toFloat()
val registrationDate = chapterJson.getStringOrNull("RegistrationDate") ?: ""
return MangaChapter(
id = identification.hashCode().toLong(),
title = "Chapter $friendlyChapterNumber",
number = number,
volume = 0,
url = "/chapter/chapterIndexControls?identification=$identification",
scanlator = null,
uploadDate = parseChapterDate(registrationDate),
branch = null,
source = source,
)
}
private fun parseChapterDate(dateString: String): Long {
return try {
chapterDateFormat.parse(dateString)?.time ?: 0L
} catch (_: Exception) {
0L
}
}
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val response = webClient.httpGet(
"https://$domain${chapter.url}".toHttpUrl(),
)
val document = response.parseHtml()
val ch = document.select("[id=\"FriendlyChapterNumberUrl\"]").attr("value")
val title = document.select("[id=\"FriendlyMangaName\"]").attr("value")
return document.select("img.ImageContainer").mapIndexed { index, img ->
val imageId = img.attr("id")
val imageUrl = "$imageCDN/images/manga/$title/chapter/$ch/page/${index + 1}/$imageId"
MangaPage(
id = index.toLong(),
url = imageUrl,
preview = null,
source = source,
)
}
}
}
Loading…
Cancel
Save