diff --git a/src/es/inmanga/build.gradle b/src/es/inmanga/build.gradle index 73438dce5..1cda268fd 100644 --- a/src/es/inmanga/build.gradle +++ b/src/es/inmanga/build.gradle @@ -1,11 +1,12 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' +apply plugin: 'kotlinx-serialization' ext { extName = 'InManga' pkgNameSuffix = 'es.inmanga' extClass = '.InManga' - extVersionCode = 1 + extVersionCode = 2 libVersion = '1.2' } diff --git a/src/es/inmanga/src/eu/kanade/tachiyomi/extension/es/inmanga/InManga.kt b/src/es/inmanga/src/eu/kanade/tachiyomi/extension/es/inmanga/InManga.kt index 2d4973f60..796951a0e 100644 --- a/src/es/inmanga/src/eu/kanade/tachiyomi/extension/es/inmanga/InManga.kt +++ b/src/es/inmanga/src/eu/kanade/tachiyomi/extension/es/inmanga/InManga.kt @@ -1,26 +1,20 @@ 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.POST -import eu.kanade.tachiyomi.source.model.FilterList -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.model.* import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.util.asJsoup +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.json.Json import okhttp3.OkHttpClient import okhttp3.Request +import okhttp3.RequestBody import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.Response import org.jsoup.nodes.Document import org.jsoup.nodes.Element +import uy.kohesive.injekt.injectLazy import java.text.SimpleDateFormat import java.util.Locale @@ -41,19 +35,25 @@ class InManga : ParsedHttpSource() { .add("X-Requested-With", "XMLHttpRequest") .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 - 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( - null - ) + /** + * Returns RequestBody to retrieve latest or populars Manga. + * + * @param page Current page number. + * @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() @@ -61,26 +61,17 @@ class InManga : ParsedHttpSource() { override fun popularMangaNextPageSelector() = "body" - // Latest - - // Search filtered by "Recién actualizado" - override fun latestUpdatesRequest(page: Int): Request { - 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 latestUpdatesRequest(page: Int) = POST( + url = "$baseUrl/manga/getMangasConsultResult", + headers = postHeaders, + body = requestBodyBuilder(page, false) + ) override fun latestUpdatesSelector() = searchMangaSelector() - override fun latestUpdatesFromElement(element: Element) = searchMangaFromElement(element) + override fun latestUpdatesFromElement(element: Element) = searchMangaFromElement(element = element) - override fun latestUpdatesNextPageSelector() = "body" - - // Search + override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { val skip = (page - 1) * 10 @@ -95,7 +86,6 @@ class InManga : ParsedHttpSource() { override fun searchMangaParse(response: Response): MangasPage { val mangas = mutableListOf() val document = response.asJsoup() - document.select(searchMangaSelector()).map { mangas.add(searchMangaFromElement(it)) } return MangasPage(mangas, document.select(searchMangaSelector()).count() == 10) @@ -103,33 +93,23 @@ class InManga : ParsedHttpSource() { override fun searchMangaSelector() = "body > a" - override fun searchMangaFromElement(element: Element): SManga { - val manga = SManga.create() - - manga.setUrlWithoutDomain(element.attr("href")) - manga.title = element.select("h4.m0").text() - manga.thumbnail_url = element.select("img").attr("abs:data-src") - - return manga + override fun searchMangaFromElement(element: Element) = SManga.create().apply { + setUrlWithoutDomain(element.attr("href")) + title = element.select("h4.m0").text() + thumbnail_url = element.select("img").attr("abs:data-src") } override fun searchMangaNextPageSelector(): String? = null - // Manga summary page - - override fun mangaDetailsParse(document: Document): SManga { - val manga = SManga.create() - + override fun mangaDetailsParse(document: Document) = SManga.create().apply { document.select("div.col-md-3 div.panel.widget").let { info -> - manga.thumbnail_url = info.select("img").attr("abs:src") - manga.status = parseStatus(info.select(" a.list-group-item:contains(estado) span").text()) + thumbnail_url = info.select("img").attr("abs:src") + status = parseStatus(info.select(" a.list-group-item:contains(estado) span").text()) } document.select("div.col-md-9").let { info -> - manga.title = info.select("h1").text() - manga.description = info.select("div.panel-body").text() + title = info.select("h1").text() + description = info.select("div.panel-body").text() } - - return manga } private fun parseStatus(status: String?) = when { @@ -139,64 +119,56 @@ class InManga : ParsedHttpSource() { else -> SManga.UNKNOWN } - // Chapters - - override fun chapterListRequest(manga: SManga): Request { - return GET("$baseUrl/chapter/getall?mangaIdentification=${manga.url.substringAfterLast("/")}", headers) - } + override fun chapterListRequest(manga: SManga) = GET( + url = "$baseUrl/chapter/getall?mangaIdentification=${manga.url.substringAfterLast("/")}", + headers = headers + ) override fun chapterListParse(response: Response): List { - val chapters = mutableListOf() - val data = response.body!!.string().substringAfter("{\"data\":\"").substringBeforeLast("\"}") - .replace("\\", "") + // The server returns a JSON with data property that contains a string with the JSON, + // so is necessary to decode twice. + val data = json.decodeFromString(response.body!!.string()) + if (data.data.isNullOrEmpty()) + return emptyList() - gson.fromJson(data)["result"].asJsonArray.forEach { chapters.add(chapterFromJson(it)) } + val result = json.decodeFromString>(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" - private fun chapterFromJson(json: JsonElement): SChapter { - val chapter = SChapter.create() - - chapter.url = "/chapter/chapterIndexControls?identification=${json["Identification"].string}" - json["FriendlyChapterNumberUrl"].string.replace("-", ".").let { num -> - chapter.name = "Chapter $num" - chapter.chapter_number = num.toFloat() - } - chapter.date_upload = parseChapterDate(json["RegistrationDate"].string) ?: 0 - - return chapter + private fun chapterFromObject(chapter: InMangaChapterDto) = SChapter.create().apply { + url = "/chapter/chapterIndexControls?identification=${chapter.identification}" + name = "Chapter ${chapter.friendlyChapterNumber}" + chapter_number = chapter.number!!.toFloat() + date_upload = parseChapterDate(chapter.registrationDate) } override fun chapterFromElement(element: Element): SChapter = throw UnsupportedOperationException("Not used") - companion object { - val dateFormat by lazy { - SimpleDateFormat("yyyy-MM-dd", Locale.US) - } + private fun parseChapterDate(string: String): Long { + return DATE_FORMATTER.parse(string)?.time ?: 0L } - private fun parseChapterDate(string: String): Long? { - return dateFormat.parse(string)?.time ?: 0L - } - - // Pages - - override fun pageListParse(document: Document): List { - val pages = mutableListOf() + override fun pageListParse(document: Document): List = mutableListOf().apply { val ch = document.select("[id=\"FriendlyChapterNumberUrl\"]").attr("value") val title = document.select("[id=\"FriendlyMangaName\"]").attr("value") 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 getFilterList() = FilterList() + + companion object { + val DATE_FORMATTER by lazy { SimpleDateFormat("yyyy-MM-dd", Locale.US) } + } } diff --git a/src/es/inmanga/src/eu/kanade/tachiyomi/extension/es/inmanga/InMangaResultDto.kt b/src/es/inmanga/src/eu/kanade/tachiyomi/extension/es/inmanga/InMangaResultDto.kt new file mode 100644 index 000000000..93e083dfb --- /dev/null +++ b/src/es/inmanga/src/eu/kanade/tachiyomi/extension/es/inmanga/InMangaResultDto.kt @@ -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( + val message: String = "", + val success: Boolean, + val result: List +) + +@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 = emptyList(), + @SerialName("Identification") val identification: String? = "", + @SerialName("FeaturedChapter") val featuredChapter: Boolean = false, + @SerialName("FriendlyChapterNumber") val friendlyChapterNumber: String? = "", + @SerialName("FriendlyChapterNumberUrl") val friendlyChapterNumberUrl: String? = "", +) diff --git a/src/es/mangamx/build.gradle b/src/es/mangamx/build.gradle index 10acd78dd..fbbdde7f9 100644 --- a/src/es/mangamx/build.gradle +++ b/src/es/mangamx/build.gradle @@ -6,7 +6,7 @@ ext { extName = 'MangaMx' pkgNameSuffix = 'es.mangamx' extClass = '.MangaMx' - extVersionCode = 11 + extVersionCode = 12 libVersion = '1.2' } diff --git a/src/es/mangamx/src/eu/kanade/tachiyomi/extension/es/mangamx/MangaMx.kt b/src/es/mangamx/src/eu/kanade/tachiyomi/extension/es/mangamx/MangaMx.kt index e22e4540a..54ff76c40 100644 --- a/src/es/mangamx/src/eu/kanade/tachiyomi/extension/es/mangamx/MangaMx.kt +++ b/src/es/mangamx/src/eu/kanade/tachiyomi/extension/es/mangamx/MangaMx.kt @@ -15,11 +15,8 @@ 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.util.asJsoup +import kotlinx.serialization.decodeFromString 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.Request import okhttp3.Response @@ -49,9 +46,8 @@ open class MangaMx : ConfigurableSource, ParsedHttpSource() { private val json: Json by injectLazy() override fun popularMangaRequest(page: Int) = GET( - "$baseUrl/directorio?genero=false" + - "&estado=false&filtro=visitas&tipo=false&adulto=${if (hideNSFWContent()) "0" else "false"}&orden=desc&p=$page", - headers + url = "$baseUrl/directorio?genero=false&estado=false&filtro=visitas&tipo=false&adulto=${if (hideNSFWContent()) "0" else "false"}&orden=desc&p=$page", + headers = headers ) override fun popularMangaNextPageSelector() = ".page-item a[rel=next]" @@ -99,16 +95,13 @@ open class MangaMx : ConfigurableSource, ParsedHttpSource() { .add("buscar", query) .add("_token", csrfToken) .build() - val searchHeaders = headers.newBuilder().add("X-Requested-With", "XMLHttpRequest") + val searchHeaders = headers.newBuilder() + .add("X-Requested-With", "XMLHttpRequest") .add("Referer", baseUrl).build() - return POST("$baseUrl/buscar", searchHeaders, formBody) + return POST(url = "$baseUrl/buscar", headers = searchHeaders, body = formBody) } else { 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) { when (filter) { @@ -146,6 +139,7 @@ open class MangaMx : ConfigurableSource, ParsedHttpSource() { override fun searchMangaParse(response: Response): MangasPage { if (!response.isSuccessful) throw Exception("Búsqueda fallida ${response.code}") + if ("directorio" in response.request.url.toString()) { val document = response.asJsoup() val mangas = document.select(searchMangaSelector()).map { element -> @@ -158,22 +152,20 @@ open class MangaMx : ConfigurableSource, ParsedHttpSource() { return MangasPage(mangas, hasNextPage) } else { - val jsonResult = json.parseToJsonElement(response.body!!.string()).jsonArray + val result = json.decodeFromString(response.body!!.string()) + if (result.mangaList.isEmpty()) throw Exception("Término de búsqueda demasiado corto") - if (jsonResult.isEmpty()) { - throw Exception("Término de búsqueda demasiado corto") - } - - val mangaList = jsonResult.map { jsonEl -> searchMangaFromJson(jsonEl.jsonObject) } + val mangaList = result.mangaList + .map(::searchMangaFromObject) return MangasPage(mangaList, hasNextPage = false) } } - private fun searchMangaFromJson(jsonObj: JsonObject): SManga = SManga.create().apply { - title = jsonObj["nombre"]!!.jsonPrimitive.content - thumbnail_url = jsonObj["img"]!!.jsonPrimitive.content.replace("/thumb", "/cover") - setUrlWithoutDomain(jsonObj["url"]!!.jsonPrimitive.content) + private fun searchMangaFromObject(manga: MangaDto): SManga = SManga.create().apply { + title = manga.name + thumbnail_url = manga.img.replace("/thumb", "/cover") + setUrlWithoutDomain(manga.url) } override fun mangaDetailsParse(document: Document): SManga { diff --git a/src/es/mangamx/src/eu/kanade/tachiyomi/extension/es/mangamx/ResponseDto.kt b/src/es/mangamx/src/eu/kanade/tachiyomi/extension/es/mangamx/ResponseDto.kt new file mode 100644 index 000000000..420c845c0 --- /dev/null +++ b/src/es/mangamx/src/eu/kanade/tachiyomi/extension/es/mangamx/ResponseDto.kt @@ -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 = emptyList(), + @SerialName("usuarios") val usersList: List? = emptyList(), + @SerialName("grupos") val groupsList: List? = 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 = "", +)