Fix InManga pages not load & MangaMx search (#7744)

* InManga replace Gson with kotlinx.serialization, fix pages not load.

* MangaMx: Fix search bug.
This commit is contained in:
Edgar Mejía 2021-06-20 06:40:21 -06:00 committed by GitHub
parent 7f9223fefd
commit 8585b5cd81
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 163 additions and 119 deletions

View File

@ -1,11 +1,12 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext { ext {
extName = 'InManga' extName = 'InManga'
pkgNameSuffix = 'es.inmanga' pkgNameSuffix = 'es.inmanga'
extClass = '.InManga' extClass = '.InManga'
extVersionCode = 1 extVersionCode = 2
libVersion = '1.2' libVersion = '1.2'
} }

View File

@ -1,26 +1,20 @@
package eu.kanade.tachiyomi.extension.es.inmanga package eu.kanade.tachiyomi.extension.es.inmanga
import com.github.salomonbrys.kotson.fromJson
import com.github.salomonbrys.kotson.get
import com.github.salomonbrys.kotson.string
import com.google.gson.Gson
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.*
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
@ -41,19 +35,25 @@ class InManga : ParsedHttpSource() {
.add("X-Requested-With", "XMLHttpRequest") .add("X-Requested-With", "XMLHttpRequest")
.build() .build()
private val gson = Gson() private val json: Json by injectLazy()
// Popular private val imageCDN = "https://pack-yak.intomanga.com/"
override fun popularMangaRequest(page: Int): Request { /**
val skip = (page - 1) * 10 * Returns RequestBody to retrieve latest or populars Manga.
val body = *
"filter%5Bgeneres%5D%5B%5D=-1&filter%5BqueryString%5D=&filter%5Bskip%5D=$skip&filter%5Btake%5D=10&filter%5Bsortby%5D=1&filter%5BbroadcastStatus%5D=0&filter%5BonlyFavorites%5D=false&d=".toRequestBody( * @param page Current page number.
null * @param isPopular If is true filter sortby = 1 else sortby = 3
) * sortby = 1: Populars
* sortby = 3: Latest
*/
private fun requestBodyBuilder(page: Int, isPopular: Boolean): RequestBody = "filter%5Bgeneres%5D%5B%5D=-1&filter%5BqueryString%5D=&filter%5Bskip%5D=${(page - 1) * 10}&filter%5Btake%5D=10&filter%5Bsortby%5D=${if (isPopular) "1" else "3"}&filter%5BbroadcastStatus%5D=0&filter%5BonlyFavorites%5D=false&d=".toRequestBody(null)
return POST("$baseUrl/manga/getMangasConsultResult", postHeaders, body) override fun popularMangaRequest(page: Int) = POST(
} url = "$baseUrl/manga/getMangasConsultResult",
headers = postHeaders,
body = requestBodyBuilder(page, true)
)
override fun popularMangaSelector() = searchMangaSelector() override fun popularMangaSelector() = searchMangaSelector()
@ -61,26 +61,17 @@ class InManga : ParsedHttpSource() {
override fun popularMangaNextPageSelector() = "body" override fun popularMangaNextPageSelector() = "body"
// Latest override fun latestUpdatesRequest(page: Int) = POST(
url = "$baseUrl/manga/getMangasConsultResult",
// Search filtered by "Recién actualizado" headers = postHeaders,
override fun latestUpdatesRequest(page: Int): Request { body = requestBodyBuilder(page, false)
val skip = (page - 1) * 10 )
val body =
"filter%5Bgeneres%5D%5B%5D=-1&filter%5BqueryString%5D=&filter%5Bskip%5D=$skip&filter%5Btake%5D=10&filter%5Bsortby%5D=3&filter%5BbroadcastStatus%5D=0&filter%5BonlyFavorites%5D=false&d=".toRequestBody(
null
)
return POST("$baseUrl/manga/getMangasConsultResult", postHeaders, body)
}
override fun latestUpdatesSelector() = searchMangaSelector() override fun latestUpdatesSelector() = searchMangaSelector()
override fun latestUpdatesFromElement(element: Element) = searchMangaFromElement(element) override fun latestUpdatesFromElement(element: Element) = searchMangaFromElement(element = element)
override fun latestUpdatesNextPageSelector() = "body" override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
// Search
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val skip = (page - 1) * 10 val skip = (page - 1) * 10
@ -95,7 +86,6 @@ class InManga : ParsedHttpSource() {
override fun searchMangaParse(response: Response): MangasPage { override fun searchMangaParse(response: Response): MangasPage {
val mangas = mutableListOf<SManga>() val mangas = mutableListOf<SManga>()
val document = response.asJsoup() val document = response.asJsoup()
document.select(searchMangaSelector()).map { mangas.add(searchMangaFromElement(it)) } document.select(searchMangaSelector()).map { mangas.add(searchMangaFromElement(it)) }
return MangasPage(mangas, document.select(searchMangaSelector()).count() == 10) return MangasPage(mangas, document.select(searchMangaSelector()).count() == 10)
@ -103,33 +93,23 @@ class InManga : ParsedHttpSource() {
override fun searchMangaSelector() = "body > a" override fun searchMangaSelector() = "body > a"
override fun searchMangaFromElement(element: Element): SManga { override fun searchMangaFromElement(element: Element) = SManga.create().apply {
val manga = SManga.create() setUrlWithoutDomain(element.attr("href"))
title = element.select("h4.m0").text()
manga.setUrlWithoutDomain(element.attr("href")) thumbnail_url = element.select("img").attr("abs:data-src")
manga.title = element.select("h4.m0").text()
manga.thumbnail_url = element.select("img").attr("abs:data-src")
return manga
} }
override fun searchMangaNextPageSelector(): String? = null override fun searchMangaNextPageSelector(): String? = null
// Manga summary page override fun mangaDetailsParse(document: Document) = SManga.create().apply {
override fun mangaDetailsParse(document: Document): SManga {
val manga = SManga.create()
document.select("div.col-md-3 div.panel.widget").let { info -> document.select("div.col-md-3 div.panel.widget").let { info ->
manga.thumbnail_url = info.select("img").attr("abs:src") thumbnail_url = info.select("img").attr("abs:src")
manga.status = parseStatus(info.select(" a.list-group-item:contains(estado) span").text()) status = parseStatus(info.select(" a.list-group-item:contains(estado) span").text())
} }
document.select("div.col-md-9").let { info -> document.select("div.col-md-9").let { info ->
manga.title = info.select("h1").text() title = info.select("h1").text()
manga.description = info.select("div.panel-body").text() description = info.select("div.panel-body").text()
} }
return manga
} }
private fun parseStatus(status: String?) = when { private fun parseStatus(status: String?) = when {
@ -139,64 +119,56 @@ class InManga : ParsedHttpSource() {
else -> SManga.UNKNOWN else -> SManga.UNKNOWN
} }
// Chapters override fun chapterListRequest(manga: SManga) = GET(
url = "$baseUrl/chapter/getall?mangaIdentification=${manga.url.substringAfterLast("/")}",
override fun chapterListRequest(manga: SManga): Request { headers = headers
return GET("$baseUrl/chapter/getall?mangaIdentification=${manga.url.substringAfterLast("/")}", headers) )
}
override fun chapterListParse(response: Response): List<SChapter> { override fun chapterListParse(response: Response): List<SChapter> {
val chapters = mutableListOf<SChapter>() // The server returns a JSON with data property that contains a string with the JSON,
val data = response.body!!.string().substringAfter("{\"data\":\"").substringBeforeLast("\"}") // so is necessary to decode twice.
.replace("\\", "") val data = json.decodeFromString<InMangaResultDto>(response.body!!.string())
if (data.data.isNullOrEmpty())
return emptyList()
gson.fromJson<JsonObject>(data)["result"].asJsonArray.forEach { chapters.add(chapterFromJson(it)) } val result = json.decodeFromString<InMangaResultObjectDto<InMangaChapterDto>>(data.data)
if (!result.success)
return emptyList()
return chapters.sortedBy { it.chapter_number.toInt() }.reversed() return result.result
.map { chap -> chapterFromObject(chap) }
.sortedBy { it.chapter_number.toInt() }.reversed()
} }
override fun chapterListSelector() = "not using" override fun chapterListSelector() = "not using"
private fun chapterFromJson(json: JsonElement): SChapter { private fun chapterFromObject(chapter: InMangaChapterDto) = SChapter.create().apply {
val chapter = SChapter.create() url = "/chapter/chapterIndexControls?identification=${chapter.identification}"
name = "Chapter ${chapter.friendlyChapterNumber}"
chapter.url = "/chapter/chapterIndexControls?identification=${json["Identification"].string}" chapter_number = chapter.number!!.toFloat()
json["FriendlyChapterNumberUrl"].string.replace("-", ".").let { num -> date_upload = parseChapterDate(chapter.registrationDate)
chapter.name = "Chapter $num"
chapter.chapter_number = num.toFloat()
}
chapter.date_upload = parseChapterDate(json["RegistrationDate"].string) ?: 0
return chapter
} }
override fun chapterFromElement(element: Element): SChapter = throw UnsupportedOperationException("Not used") override fun chapterFromElement(element: Element): SChapter = throw UnsupportedOperationException("Not used")
companion object { private fun parseChapterDate(string: String): Long {
val dateFormat by lazy { return DATE_FORMATTER.parse(string)?.time ?: 0L
SimpleDateFormat("yyyy-MM-dd", Locale.US)
}
} }
private fun parseChapterDate(string: String): Long? { override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply {
return dateFormat.parse(string)?.time ?: 0L
}
// Pages
override fun pageListParse(document: Document): List<Page> {
val pages = mutableListOf<Page>()
val ch = document.select("[id=\"FriendlyChapterNumberUrl\"]").attr("value") val ch = document.select("[id=\"FriendlyChapterNumberUrl\"]").attr("value")
val title = document.select("[id=\"FriendlyMangaName\"]").attr("value") val title = document.select("[id=\"FriendlyMangaName\"]").attr("value")
document.select("img.ImageContainer").forEachIndexed { i, img -> document.select("img.ImageContainer").forEachIndexed { i, img ->
pages.add(Page(i, "", "$baseUrl/images/manga/$title/chapter/$ch/page/${i + 1}/${img.attr("id")}")) add(Page(i, "", "$imageCDN/images/manga/$title/chapter/$ch/page/${i + 1}/${img.attr("id")}"))
} }
return pages
} }
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used") override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used")
override fun getFilterList() = FilterList() override fun getFilterList() = FilterList()
companion object {
val DATE_FORMATTER by lazy { SimpleDateFormat("yyyy-MM-dd", Locale.US) }
}
} }

View File

@ -0,0 +1,35 @@
package eu.kanade.tachiyomi.extension.es.inmanga
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class InMangaResultDto(
val data: String?
)
@Serializable
data class InMangaResultObjectDto<T>(
val message: String = "",
val success: Boolean,
val result: List<T>
)
@Serializable
data class InMangaChapterDto(
@SerialName("PagesCount") val pagesCount: Int = 0,
@SerialName("Watched") val watched: Boolean? = false,
@SerialName("MangaIdentification") val mangaIdentification: String? = "",
@SerialName("MangaName") val mangaName: String? = "",
@SerialName("FriendlyMangaName") val friendlyMangaName: String? = "",
@SerialName("Id") val id: Int? = 0,
@SerialName("MangaId") val mangaId: Int? = 0,
@SerialName("Number") val number: Double? = null,
@SerialName("RegistrationDate") val registrationDate: String = "",
@SerialName("Description") val description: String? = "",
@SerialName("Pages") val pages: List<Int> = emptyList(),
@SerialName("Identification") val identification: String? = "",
@SerialName("FeaturedChapter") val featuredChapter: Boolean = false,
@SerialName("FriendlyChapterNumber") val friendlyChapterNumber: String? = "",
@SerialName("FriendlyChapterNumberUrl") val friendlyChapterNumberUrl: String? = "",
)

View File

@ -6,7 +6,7 @@ ext {
extName = 'MangaMx' extName = 'MangaMx'
pkgNameSuffix = 'es.mangamx' pkgNameSuffix = 'es.mangamx'
extClass = '.MangaMx' extClass = '.MangaMx'
extVersionCode = 11 extVersionCode = 12
libVersion = '1.2' libVersion = '1.2'
} }

View File

@ -15,11 +15,8 @@ import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
@ -49,9 +46,8 @@ open class MangaMx : ConfigurableSource, ParsedHttpSource() {
private val json: Json by injectLazy() private val json: Json by injectLazy()
override fun popularMangaRequest(page: Int) = GET( override fun popularMangaRequest(page: Int) = GET(
"$baseUrl/directorio?genero=false" + url = "$baseUrl/directorio?genero=false&estado=false&filtro=visitas&tipo=false&adulto=${if (hideNSFWContent()) "0" else "false"}&orden=desc&p=$page",
"&estado=false&filtro=visitas&tipo=false&adulto=${if (hideNSFWContent()) "0" else "false"}&orden=desc&p=$page", headers = headers
headers
) )
override fun popularMangaNextPageSelector() = ".page-item a[rel=next]" override fun popularMangaNextPageSelector() = ".page-item a[rel=next]"
@ -99,16 +95,13 @@ open class MangaMx : ConfigurableSource, ParsedHttpSource() {
.add("buscar", query) .add("buscar", query)
.add("_token", csrfToken) .add("_token", csrfToken)
.build() .build()
val searchHeaders = headers.newBuilder().add("X-Requested-With", "XMLHttpRequest") val searchHeaders = headers.newBuilder()
.add("X-Requested-With", "XMLHttpRequest")
.add("Referer", baseUrl).build() .add("Referer", baseUrl).build()
return POST("$baseUrl/buscar", searchHeaders, formBody) return POST(url = "$baseUrl/buscar", headers = searchHeaders, body = formBody)
} else { } else {
val uri = Uri.parse("$baseUrl/directorio").buildUpon() val uri = Uri.parse("$baseUrl/directorio").buildUpon()
uri.appendQueryParameter("adulto", if (hideNSFWContent()) { "0" } else { "1" })
uri.appendQueryParameter(
"adulto",
if (hideNSFWContent()) { "0" } else { "1" }
)
for (filter in filters) { for (filter in filters) {
when (filter) { when (filter) {
@ -146,6 +139,7 @@ open class MangaMx : ConfigurableSource, ParsedHttpSource() {
override fun searchMangaParse(response: Response): MangasPage { override fun searchMangaParse(response: Response): MangasPage {
if (!response.isSuccessful) throw Exception("Búsqueda fallida ${response.code}") if (!response.isSuccessful) throw Exception("Búsqueda fallida ${response.code}")
if ("directorio" in response.request.url.toString()) { if ("directorio" in response.request.url.toString()) {
val document = response.asJsoup() val document = response.asJsoup()
val mangas = document.select(searchMangaSelector()).map { element -> val mangas = document.select(searchMangaSelector()).map { element ->
@ -158,22 +152,20 @@ open class MangaMx : ConfigurableSource, ParsedHttpSource() {
return MangasPage(mangas, hasNextPage) return MangasPage(mangas, hasNextPage)
} else { } else {
val jsonResult = json.parseToJsonElement(response.body!!.string()).jsonArray val result = json.decodeFromString<ResponseDto>(response.body!!.string())
if (result.mangaList.isEmpty()) throw Exception("Término de búsqueda demasiado corto")
if (jsonResult.isEmpty()) { val mangaList = result.mangaList
throw Exception("Término de búsqueda demasiado corto") .map(::searchMangaFromObject)
}
val mangaList = jsonResult.map { jsonEl -> searchMangaFromJson(jsonEl.jsonObject) }
return MangasPage(mangaList, hasNextPage = false) return MangasPage(mangaList, hasNextPage = false)
} }
} }
private fun searchMangaFromJson(jsonObj: JsonObject): SManga = SManga.create().apply { private fun searchMangaFromObject(manga: MangaDto): SManga = SManga.create().apply {
title = jsonObj["nombre"]!!.jsonPrimitive.content title = manga.name
thumbnail_url = jsonObj["img"]!!.jsonPrimitive.content.replace("/thumb", "/cover") thumbnail_url = manga.img.replace("/thumb", "/cover")
setUrlWithoutDomain(jsonObj["url"]!!.jsonPrimitive.content) setUrlWithoutDomain(manga.url)
} }
override fun mangaDetailsParse(document: Document): SManga { override fun mangaDetailsParse(document: Document): SManga {

View File

@ -0,0 +1,44 @@
package eu.kanade.tachiyomi.extension.es.mangamx
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class ResponseDto(
@SerialName("mangas") val mangaList: List<MangaDto> = emptyList(),
@SerialName("usuarios") val usersList: List<UserDto>? = emptyList(),
@SerialName("grupos") val groupsList: List<GroupDto>? = emptyList(),
)
@Serializable
data class MangaDto(
@SerialName("nombre") val name: String = "",
@SerialName("alterno") val alternative: String? = "",
@SerialName("tipo") val type: Int = 0,
@SerialName("lanzamiento") val releaseYear: Int = 0,
@SerialName("autor") val author: String? = "",
val visible: Int = 0,
val cover: String? = "",
val slug: String = "",
val url: String = "",
val img: String = "",
)
@Serializable
data class UserDto(
@SerialName("usuario") val username: String = "",
@SerialName("perfil") val profile: String? = "",
@SerialName("genero") val gender: String? = "",
val id: Int? = 0,
val url: String = "",
val img: String = "",
)
@Serializable
data class GroupDto(
@SerialName("nombre") val name: String = "",
val id: Int? = 0,
val cover: String? = "",
val url: String = "",
val img: String = "",
)