From b993afac8d78c5aeb960ae16cdb939710ad46437 Mon Sep 17 00:00:00 2001 From: Alessandro Jean Date: Wed, 9 Jan 2019 22:56:12 -0200 Subject: [PATCH] Fixes for brazilian sources (#716) Fixes for brazilian sources --- src/pt/mangalivre/build.gradle | 2 +- .../extension/pt/mangalivre/MangaLivre.kt | 509 +++++++++--------- src/pt/mangasproject/build.gradle | 2 +- .../pt/mangasproject/MangasProject.kt | 509 +++++++++--------- src/pt/unionmangas/build.gradle | 2 +- .../extension/pt/unionmangas/UnionMangas.kt | 45 +- 6 files changed, 541 insertions(+), 528 deletions(-) diff --git a/src/pt/mangalivre/build.gradle b/src/pt/mangalivre/build.gradle index 034e8fc29..9268c1970 100644 --- a/src/pt/mangalivre/build.gradle +++ b/src/pt/mangalivre/build.gradle @@ -5,7 +5,7 @@ ext { appName = 'Tachiyomi: Mangá Livre' pkgNameSuffix = 'pt.mangalivre' extClass = '.MangaLivre' - extVersionCode = 1 + extVersionCode = 2 libVersion = '1.2' } diff --git a/src/pt/mangalivre/src/eu/kanade/tachiyomi/extension/pt/mangalivre/MangaLivre.kt b/src/pt/mangalivre/src/eu/kanade/tachiyomi/extension/pt/mangalivre/MangaLivre.kt index 0b6549f6f..9cc043652 100644 --- a/src/pt/mangalivre/src/eu/kanade/tachiyomi/extension/pt/mangalivre/MangaLivre.kt +++ b/src/pt/mangalivre/src/eu/kanade/tachiyomi/extension/pt/mangalivre/MangaLivre.kt @@ -22,293 +22,294 @@ import java.util.* import java.util.concurrent.TimeUnit class MangaLivre : HttpSource() { - override val name = "MangaLivre" + override val name = "MangaLivre" - override val baseUrl = "https://mangalivre.com/" + override val baseUrl = "https://mangalivre.com" - override val lang = "pt" + override val lang = "pt" - override val supportsLatest = true + override val supportsLatest = true - // Sometimes the site is slow. - override val client = network.client.newBuilder() - .connectTimeout(1, TimeUnit.MINUTES) - .readTimeout(1, TimeUnit.MINUTES) - .writeTimeout(1, TimeUnit.MINUTES) - .build()!! + // Sometimes the site is slow. + override val client = + network.client.newBuilder() + .connectTimeout(1, TimeUnit.MINUTES) + .readTimeout(1, TimeUnit.MINUTES) + .writeTimeout(1, TimeUnit.MINUTES) + .build() - private val catalogHeaders = Headers.Builder().apply { - add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36") - add("Host", "mangalivre.com") - // The API doesn't return the result if this header isn't sent. - add("X-Requested-With", "XMLHttpRequest") - }.build() + private val catalogHeaders = Headers.Builder().apply { + add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36") + add("Host", "mangalivre.com") + // The API doesn't return the result if this header isn't sent. + add("X-Requested-With", "XMLHttpRequest") + }.build() - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/home/most_read?page=$page", catalogHeaders) - } - - override fun popularMangaParse(response: Response): MangasPage { - val result = jsonParser.parse(response.body()!!.string()).obj - - // If "most_read" have boolean false value, then it doesn't have next page. - if (!result["most_read"]!!.isJsonArray) - return MangasPage(emptyList(), false) - - val popularMangas = result.getAsJsonArray("most_read")?.map { - popularMangaItemParse(it.obj) + override fun popularMangaRequest(page: Int): Request { + return GET("$baseUrl/home/most_read?page=$page", catalogHeaders) } - val hasNextPage = response.request().url().queryParameter("page")!!.toInt() < 10 + override fun popularMangaParse(response: Response): MangasPage { + val result = jsonParser.parse(response.body()!!.string()).obj - if (popularMangas != null) - return MangasPage(popularMangas, hasNextPage) + // If "most_read" have boolean false value, then it doesn't have next page. + if (!result["most_read"]!!.isJsonArray) + return MangasPage(emptyList(), false) - return MangasPage(emptyList(), false) - } + val popularMangas = result.getAsJsonArray("most_read")?.map { + popularMangaItemParse(it.obj) + } - private fun popularMangaItemParse(obj: JsonObject) = SManga.create().apply { - title = obj["serie_name"].nullString ?: "" - thumbnail_url = obj["cover"].nullString - url = obj["link"].nullString ?: "" - } + val hasNextPage = response.request().url().queryParameter("page")!!.toInt() < 10 - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/home/releases?page=$page", catalogHeaders) - } + if (popularMangas != null) + return MangasPage(popularMangas, hasNextPage) - override fun latestUpdatesParse(response: Response): MangasPage { - if (response.code() == 500) - return MangasPage(emptyList(), false) - - val result = jsonParser.parse(response.body()!!.string()).obj - - val latestMangas = result.getAsJsonArray("releases")?.map { - latestMangaItemParse(it.obj) + return MangasPage(emptyList(), false) } - val hasNextPage = response.request().url().queryParameter("page")!!.toInt() < 5 - - if (latestMangas != null) - return MangasPage(latestMangas, hasNextPage) - - return MangasPage(emptyList(), false) - } - - private fun latestMangaItemParse(obj: JsonObject) = SManga.create().apply { - title = obj["name"].nullString ?: "" - thumbnail_url = obj["image"].nullString - url = obj["link"].nullString ?: "" - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val form = FormBody.Builder().apply { - add("search", query) + private fun popularMangaItemParse(obj: JsonObject) = SManga.create().apply { + title = obj["serie_name"].nullString ?: "" + thumbnail_url = obj["cover"].nullString + url = obj["link"].nullString ?: "" } - return POST("$baseUrl/lib/search/series.json", catalogHeaders, form.build()) - } - - override fun searchMangaParse(response: Response): MangasPage { - val result = jsonParser.parse(response.body()!!.string()).obj - - // If "series" have boolean false value, then it doesn't have results. - if (!result["series"]!!.isJsonArray) - return MangasPage(emptyList(), false) - - val searchMangas = result.getAsJsonArray("series")?.map { - searchMangaItemParse(it.obj) + override fun latestUpdatesRequest(page: Int): Request { + return GET("$baseUrl/home/releases?page=$page", catalogHeaders) } - if (searchMangas != null) - return MangasPage(searchMangas, false) + override fun latestUpdatesParse(response: Response): MangasPage { + if (response.code() == 500) + return MangasPage(emptyList(), false) - return MangasPage(emptyList(), false) - } + val result = jsonParser.parse(response.body()!!.string()).obj - private fun searchMangaItemParse(obj: JsonObject) = SManga.create().apply { - title = obj["name"].nullString ?: "" - thumbnail_url = obj["cover"].nullString - url = obj["link"].nullString ?: "" - author = obj["author"].nullString - artist = obj["artist"].nullString - } + val latestMangas = result.getAsJsonArray("releases")?.map { + latestMangaItemParse(it.obj) + } - override fun mangaDetailsParse(response: Response): SManga { - val document = response.asJsoup() - val isCompleted = document.select("div#series-data span.series-author i.complete-series").first() != null - val cGenre = document.select("div#series-data ul.tags li").joinToString { it!!.text() } + val hasNextPage = response.request().url().queryParameter("page")!!.toInt() < 5 - val seriesAuthor = if (isCompleted) { - document.select("div#series-data span.series-author").first()!!.nextSibling().toString().substringBeforeLast("+") - } else { - document.select("div#series-data span.series-author").first()!!.text().substringBeforeLast("+") + if (latestMangas != null) + return MangasPage(latestMangas, hasNextPage) + + return MangasPage(emptyList(), false) } - val authors = seriesAuthor.split("&") - .map { it.trim() } - - val cAuthor = authors.filter { !it.contains("(Arte)") } - .map { author -> - if (author.contains(", ")) { - val authorSplit = author.split(", ") - authorSplit[1] + " " + authorSplit[0] - } else { - author - } - } - - val cArtist = authors.filter { it.contains("(Arte)") } - .map { it.replace("\\(Arte\\)".toRegex(), "").trim() } - .map { author -> - if (author.contains(", ")) { - val authorSplit = author.split(", ") - authorSplit[1] + " " + authorSplit[0] - } else { - author - } - } - - // Check if the manga was removed by the publisher. - val cStatus = if (document.select("div.series-blocked-img").first() == null) { - if (isCompleted) SManga.COMPLETED else SManga.ONGOING - } else { - SManga.LICENSED + private fun latestMangaItemParse(obj: JsonObject) = SManga.create().apply { + title = obj["name"].nullString ?: "" + thumbnail_url = obj["image"].nullString + url = obj["link"].nullString ?: "" } - return SManga.create().apply { - genre = cGenre - status = cStatus - description = document.select("div#series-data span.series-desc").first()?.text() - author = cAuthor.joinToString("; ") - artist = if (cArtist.isEmpty()) cAuthor.joinToString("; ") else cArtist.joinToString("; ") + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val form = FormBody.Builder().apply { + add("search", query) + } + + return POST("$baseUrl/lib/search/series.json", catalogHeaders, form.build()) } - } - // Need to override because the chapter API is paginated. - // Adapted from: - // https://stackoverflow.com/questions/35254323/rxjs-observable-pagination - // https://stackoverflow.com/questions/40529232/angular-2-http-observables-and-recursive-requests - override fun fetchChapterList(manga: SManga): Observable> { - return if (manga.status != SManga.LICENSED) { - fetchChapterList(manga, 1) - } else { - Observable.error(Exception("Licensed - No chapters to show")) + override fun searchMangaParse(response: Response): MangasPage { + val result = jsonParser.parse(response.body()!!.string()).obj + + // If "series" have boolean false value, then it doesn't have results. + if (!result["series"]!!.isJsonArray) + return MangasPage(emptyList(), false) + + val searchMangas = result.getAsJsonArray("series")?.map { + searchMangaItemParse(it.obj) + } + + if (searchMangas != null) + return MangasPage(searchMangas, false) + + return MangasPage(emptyList(), false) } - } - private fun fetchChapterList(manga: SManga, page: Int, - pastChapters: List = emptyList()): Observable> { - val chapters = pastChapters.toMutableList() - return fetchChapterListPage(manga, page) - .flatMap { - chapters += it - if (it.isEmpty()) { - Observable.just(chapters) - } else { - fetchChapterList(manga, page + 1, chapters) - } - } - } - - private fun fetchChapterListPage(manga: SManga, page: Int): Observable> { - return client.newCall(chapterListRequest(manga, page)) - .asObservableSuccess() - .map { response -> - chapterListParse(response) - } - } - - override fun chapterListRequest(manga: SManga): Request { - return chapterListRequest(manga, 1) - } - - private fun chapterListRequest(manga: SManga, page: Int): Request { - val id = manga.url.substringAfterLast("/") - return GET("$baseUrl/series/chapters_list.json?page=$page&id_serie=$id", catalogHeaders) - } - - override fun chapterListParse(response: Response): List { - val result = jsonParser.parse(response.body()!!.string()).obj - - if (!result["chapters"]!!.isJsonArray) - return emptyList() - - return result.getAsJsonArray("chapters")?.map { - chapterListItemParse(it.obj) - } ?: emptyList() - } - - private fun chapterListItemParse(obj: JsonObject): SChapter { - val scan = obj["releases"]!!.asJsonObject!!.entrySet().first().value.obj - val cName = obj["chapter_name"]!!.asString - - return SChapter.create().apply { - name = if (cName == "") "Capítulo " + obj["number"]!!.asString else cName - date_upload = parseChapterDate(obj["date"].nullString) - scanlator = scan["scanlators"]!!.asJsonArray.get(0)!!.asJsonObject["name"].nullString - url = scan["link"]!!.nullString ?: "" - chapter_number = obj["number"]!!.asString.toFloatOrNull() ?: "1".toFloat() + private fun searchMangaItemParse(obj: JsonObject) = SManga.create().apply { + title = obj["name"].nullString ?: "" + thumbnail_url = obj["cover"].nullString + url = obj["link"].nullString ?: "" + author = obj["author"].nullString + artist = obj["artist"].nullString } - } - private fun parseChapterDate(date: String?) : Long { - return try { - SimpleDateFormat("dd/MM/yyyy", Locale.ENGLISH).parse(date).time - } catch (e: ParseException) { - 0L + override fun mangaDetailsParse(response: Response): SManga { + val document = response.asJsoup() + val isCompleted = document.select("div#series-data span.series-author i.complete-series").first() != null + val cGenre = document.select("div#series-data ul.tags li").joinToString { it!!.text() } + + val seriesAuthor = if (isCompleted) { + document.select("div#series-data span.series-author").first()!!.nextSibling().toString().substringBeforeLast("+") + } else { + document.select("div#series-data span.series-author").first()!!.text().substringBeforeLast("+") + } + + val authors = seriesAuthor.split("&") + .map { it.trim() } + + val cAuthor = authors.filter { !it.contains("(Arte)") } + .map { author -> + if (author.contains(", ")) { + val authorSplit = author.split(", ") + authorSplit[1] + " " + authorSplit[0] + } else { + author + } + } + + val cArtist = authors.filter { it.contains("(Arte)") } + .map { it.replace("\\(Arte\\)".toRegex(), "").trim() } + .map { author -> + if (author.contains(", ")) { + val authorSplit = author.split(", ") + authorSplit[1] + " " + authorSplit[0] + } else { + author + } + } + + // Check if the manga was removed by the publisher. + val cStatus = if (document.select("div.series-blocked-img").first() == null) { + if (isCompleted) SManga.COMPLETED else SManga.ONGOING + } else { + SManga.LICENSED + } + + return SManga.create().apply { + genre = cGenre + status = cStatus + description = document.select("div#series-data span.series-desc").first()?.text() + author = cAuthor.joinToString("; ") + artist = if (cArtist.isEmpty()) cAuthor.joinToString("; ") else cArtist.joinToString("; ") + } } - } - override fun fetchPageList(chapter: SChapter): Observable> { - return client.newCall(pageListRequest(chapter)) - .asObservableSuccess() - .flatMap { response -> - val token = getReaderToken(response) - return@flatMap if (token == "") - Observable.error(Exception("Licensed - No chapter to show")) - else fetchPageListApi(chapter, token) - } - } - - private fun fetchPageListApi(chapter: SChapter, token: String): Observable> { - val id = "\\/(\\d+)\\/capitulo".toRegex().find(chapter.url)?.groupValues?.get(1) ?: "" - return client.newCall(pageListApiRequest(id, token)) - .asObservableSuccess() - .map { response -> - pageListParse(response) - } - } - - private fun pageListApiRequest(id: String, token: String): Request { - return GET("$baseUrl/leitor/pages/$id.json?key=$token", catalogHeaders) - } - - override fun pageListParse(response: Response): List { - val result = jsonParser.parse(response.body()!!.string()).obj - - return result["images"]!!.asJsonArray - .mapIndexed { i, obj -> - Page(i, obj.asString, obj.asString) - } - } - - override fun fetchImageUrl(page: Page): Observable { - return Observable.just(page.imageUrl!!) - } - - override fun imageUrlParse(response: Response): String = "" - - private fun getReaderToken(response: Response): String { - val document = response.asJsoup() - // The pages API needs the token provided in the reader script. - val scriptSrc = document.select("script[src*=\"reader.min.js\"]")?.first()?.attr("src") ?: "" - return "token=(.*)&id".toRegex().find(scriptSrc)?.groupValues?.get(1) ?: "" - } - - companion object { - val jsonParser by lazy { - JsonParser() + // Need to override because the chapter API is paginated. + // Adapted from: + // https://stackoverflow.com/questions/35254323/rxjs-observable-pagination + // https://stackoverflow.com/questions/40529232/angular-2-http-observables-and-recursive-requests + override fun fetchChapterList(manga: SManga): Observable> { + return if (manga.status != SManga.LICENSED) { + fetchChapterList(manga, 1) + } else { + Observable.error(Exception("Licensed - No chapters to show")) + } } - } -} \ No newline at end of file + + private fun fetchChapterList(manga: SManga, page: Int, + pastChapters: List = emptyList()): Observable> { + val chapters = pastChapters.toMutableList() + return fetchChapterListPage(manga, page) + .flatMap { + chapters += it + if (it.isEmpty()) { + Observable.just(chapters) + } else { + fetchChapterList(manga, page + 1, chapters) + } + } + } + + private fun fetchChapterListPage(manga: SManga, page: Int): Observable> { + return client.newCall(chapterListRequest(manga, page)) + .asObservableSuccess() + .map { response -> + chapterListParse(response) + } + } + + override fun chapterListRequest(manga: SManga): Request { + return chapterListRequest(manga, 1) + } + + private fun chapterListRequest(manga: SManga, page: Int): Request { + val id = manga.url.substringAfterLast("/") + return GET("$baseUrl/series/chapters_list.json?page=$page&id_serie=$id", catalogHeaders) + } + + override fun chapterListParse(response: Response): List { + val result = jsonParser.parse(response.body()!!.string()).obj + + if (!result["chapters"]!!.isJsonArray) + return emptyList() + + return result.getAsJsonArray("chapters")?.map { + chapterListItemParse(it.obj) + } ?: emptyList() + } + + private fun chapterListItemParse(obj: JsonObject): SChapter { + val scan = obj["releases"]!!.asJsonObject!!.entrySet().first().value.obj + val cName = obj["chapter_name"]!!.asString + + return SChapter.create().apply { + name = "Cap. ${obj["number"]!!.asString}" + (if (cName == "") "" else " - $cName") + date_upload = parseChapterDate(obj["date"].nullString) + scanlator = scan["scanlators"]!!.asJsonArray.get(0)!!.asJsonObject["name"].nullString + url = scan["link"]!!.nullString ?: "" + chapter_number = obj["number"]!!.asString.toFloatOrNull() ?: "1".toFloat() + } + } + + private fun parseChapterDate(date: String?) : Long { + return try { + SimpleDateFormat("dd/MM/yy", Locale.ENGLISH).parse(date).time + } catch (e: ParseException) { + 0L + } + } + + override fun fetchPageList(chapter: SChapter): Observable> { + return client.newCall(pageListRequest(chapter)) + .asObservableSuccess() + .flatMap { response -> + val token = getReaderToken(response) + return@flatMap if (token == "") + Observable.error(Exception("Licensed - No chapter to show")) + else fetchPageListApi(chapter, token) + } + } + + private fun fetchPageListApi(chapter: SChapter, token: String): Observable> { + val id = "\\/(\\d+)\\/capitulo".toRegex().find(chapter.url)?.groupValues?.get(1) ?: "" + return client.newCall(pageListApiRequest(id, token)) + .asObservableSuccess() + .map { response -> + pageListParse(response) + } + } + + private fun pageListApiRequest(id: String, token: String): Request { + return GET("$baseUrl/leitor/pages/$id.json?key=$token", catalogHeaders) + } + + override fun pageListParse(response: Response): List { + val result = jsonParser.parse(response.body()!!.string()).obj + + return result["images"]!!.asJsonArray + .mapIndexed { i, obj -> + Page(i, obj.asString, obj.asString) + } + } + + override fun fetchImageUrl(page: Page): Observable { + return Observable.just(page.imageUrl!!) + } + + override fun imageUrlParse(response: Response): String = "" + + private fun getReaderToken(response: Response): String { + val document = response.asJsoup() + // The pages API needs the token provided in the reader script. + val scriptSrc = document.select("script[src*=\"reader.min.js\"]")?.first()?.attr("src") ?: "" + return "token=(.*)&id".toRegex().find(scriptSrc)?.groupValues?.get(1) ?: "" + } + + companion object { + val jsonParser by lazy { + JsonParser() + } + } +} diff --git a/src/pt/mangasproject/build.gradle b/src/pt/mangasproject/build.gradle index 309983934..f076ece76 100644 --- a/src/pt/mangasproject/build.gradle +++ b/src/pt/mangasproject/build.gradle @@ -5,7 +5,7 @@ ext { appName = 'Tachiyomi: mangásPROJECT' pkgNameSuffix = 'pt.mangasproject' extClass = '.MangasProject' - extVersionCode = 1 + extVersionCode = 2 libVersion = '1.2' } diff --git a/src/pt/mangasproject/src/eu/kanade/tachiyomi/extension/pt/mangasproject/MangasProject.kt b/src/pt/mangasproject/src/eu/kanade/tachiyomi/extension/pt/mangasproject/MangasProject.kt index 862315d3e..3f3fd34bf 100644 --- a/src/pt/mangasproject/src/eu/kanade/tachiyomi/extension/pt/mangasproject/MangasProject.kt +++ b/src/pt/mangasproject/src/eu/kanade/tachiyomi/extension/pt/mangasproject/MangasProject.kt @@ -22,293 +22,294 @@ import java.util.* import java.util.concurrent.TimeUnit class MangasProject : HttpSource() { - override val name = "mangásPROJECT" + override val name = "mangásPROJECT" - override val baseUrl = "https://leitor.net" + override val baseUrl = "https://leitor.net" - override val lang = "pt" + override val lang = "pt" - override val supportsLatest = true + override val supportsLatest = true - // Sometimes the site is slow. - override val client = network.client.newBuilder() - .connectTimeout(1, TimeUnit.MINUTES) - .readTimeout(1, TimeUnit.MINUTES) - .writeTimeout(1, TimeUnit.MINUTES) - .build()!! + // Sometimes the site is slow. + override val client = + network.client.newBuilder() + .connectTimeout(1, TimeUnit.MINUTES) + .readTimeout(1, TimeUnit.MINUTES) + .writeTimeout(1, TimeUnit.MINUTES) + .build() - private val catalogHeaders = Headers.Builder().apply { - add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36") - add("Host", "leitor.net") - // The API doesn't return the result if this header isn't sent. - add("X-Requested-With", "XMLHttpRequest") - }.build() + private val catalogHeaders = Headers.Builder().apply { + add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36") + add("Host", "leitor.net") + // The API doesn't return the result if this header isn't sent. + add("X-Requested-With", "XMLHttpRequest") + }.build() - override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/home/most_read?page=$page", catalogHeaders) - } - - override fun popularMangaParse(response: Response): MangasPage { - val result = jsonParser.parse(response.body()!!.string()).obj - - // If "most_read" have boolean false value, then it doesn't have next page. - if (!result["most_read"]!!.isJsonArray) - return MangasPage(emptyList(), false) - - val popularMangas = result.getAsJsonArray("most_read")?.map { - popularMangaItemParse(it.obj) + override fun popularMangaRequest(page: Int): Request { + return GET("$baseUrl/home/most_read?page=$page", catalogHeaders) } - val hasNextPage = response.request().url().queryParameter("page")!!.toInt() < 10 + override fun popularMangaParse(response: Response): MangasPage { + val result = jsonParser.parse(response.body()!!.string()).obj - if (popularMangas != null) - return MangasPage(popularMangas, hasNextPage) + // If "most_read" have boolean false value, then it doesn't have next page. + if (!result["most_read"]!!.isJsonArray) + return MangasPage(emptyList(), false) - return MangasPage(emptyList(), false) - } + val popularMangas = result.getAsJsonArray("most_read")?.map { + popularMangaItemParse(it.obj) + } - private fun popularMangaItemParse(obj: JsonObject) = SManga.create().apply { - title = obj["serie_name"].nullString ?: "" - thumbnail_url = obj["cover"].nullString - url = obj["link"].nullString ?: "" - } + val hasNextPage = response.request().url().queryParameter("page")!!.toInt() < 10 - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/home/releases?page=$page", catalogHeaders) - } + if (popularMangas != null) + return MangasPage(popularMangas, hasNextPage) - override fun latestUpdatesParse(response: Response): MangasPage { - if (response.code() == 500) - return MangasPage(emptyList(), false) - - val result = jsonParser.parse(response.body()!!.string()).obj - - val latestMangas = result.getAsJsonArray("releases")?.map { - latestMangaItemParse(it.obj) + return MangasPage(emptyList(), false) } - val hasNextPage = response.request().url().queryParameter("page")!!.toInt() < 5 - - if (latestMangas != null) - return MangasPage(latestMangas, hasNextPage) - - return MangasPage(emptyList(), false) - } - - private fun latestMangaItemParse(obj: JsonObject) = SManga.create().apply { - title = obj["name"].nullString ?: "" - thumbnail_url = obj["image"].nullString - url = obj["link"].nullString ?: "" - } - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val form = FormBody.Builder().apply { - add("search", query) + private fun popularMangaItemParse(obj: JsonObject) = SManga.create().apply { + title = obj["serie_name"].nullString ?: "" + thumbnail_url = obj["cover"].nullString + url = obj["link"].nullString ?: "" } - return POST("$baseUrl/lib/search/series.json", catalogHeaders, form.build()) - } - - override fun searchMangaParse(response: Response): MangasPage { - val result = jsonParser.parse(response.body()!!.string()).obj - - // If "series" have boolean false value, then it doesn't have results. - if (!result["series"]!!.isJsonArray) - return MangasPage(emptyList(), false) - - val searchMangas = result.getAsJsonArray("series")?.map { - searchMangaItemParse(it.obj) + override fun latestUpdatesRequest(page: Int): Request { + return GET("$baseUrl/home/releases?page=$page", catalogHeaders) } - if (searchMangas != null) - return MangasPage(searchMangas, false) + override fun latestUpdatesParse(response: Response): MangasPage { + if (response.code() == 500) + return MangasPage(emptyList(), false) - return MangasPage(emptyList(), false) - } + val result = jsonParser.parse(response.body()!!.string()).obj - private fun searchMangaItemParse(obj: JsonObject) = SManga.create().apply { - title = obj["name"].nullString ?: "" - thumbnail_url = obj["cover"].nullString - url = obj["link"].nullString ?: "" - author = obj["author"].nullString - artist = obj["artist"].nullString - } + val latestMangas = result.getAsJsonArray("releases")?.map { + latestMangaItemParse(it.obj) + } - override fun mangaDetailsParse(response: Response): SManga { - val document = response.asJsoup() - val isCompleted = document.select("div#series-data span.series-author i.complete-series").first() != null - val cGenre = document.select("div#series-data ul.tags li").joinToString { it!!.text() } + val hasNextPage = response.request().url().queryParameter("page")!!.toInt() < 5 - val seriesAuthor = if (isCompleted) { - document.select("div#series-data span.series-author").first()!!.nextSibling().toString().substringBeforeLast("+") - } else { - document.select("div#series-data span.series-author").first()!!.text().substringBeforeLast("+") + if (latestMangas != null) + return MangasPage(latestMangas, hasNextPage) + + return MangasPage(emptyList(), false) } - val authors = seriesAuthor.split("&") - .map { it.trim() } - - val cAuthor = authors.filter { !it.contains("(Arte)") } - .map { author -> - if (author.contains(", ")) { - val authorSplit = author.split(", ") - authorSplit[1] + " " + authorSplit[0] - } else { - author - } - } - - val cArtist = authors.filter { it.contains("(Arte)") } - .map { it.replace("\\(Arte\\)".toRegex(), "").trim() } - .map { author -> - if (author.contains(", ")) { - val authorSplit = author.split(", ") - authorSplit[1] + " " + authorSplit[0] - } else { - author - } - } - - // Check if the manga was removed by the publisher. - val cStatus = if (document.select("div.series-blocked-img").first() == null) { - if (isCompleted) SManga.COMPLETED else SManga.ONGOING - } else { - SManga.LICENSED + private fun latestMangaItemParse(obj: JsonObject) = SManga.create().apply { + title = obj["name"].nullString ?: "" + thumbnail_url = obj["image"].nullString + url = obj["link"].nullString ?: "" } - return SManga.create().apply { - genre = cGenre - status = cStatus - description = document.select("div#series-data span.series-desc").first()?.text() - author = cAuthor.joinToString("; ") - artist = if (cArtist.isEmpty()) cAuthor.joinToString("; ") else cArtist.joinToString("; ") + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val form = FormBody.Builder().apply { + add("search", query) + } + + return POST("$baseUrl/lib/search/series.json", catalogHeaders, form.build()) } - } - // Need to override because the chapter API is paginated. - // Adapted from: - // https://stackoverflow.com/questions/35254323/rxjs-observable-pagination - // https://stackoverflow.com/questions/40529232/angular-2-http-observables-and-recursive-requests - override fun fetchChapterList(manga: SManga): Observable> { - return if (manga.status != SManga.LICENSED) { - fetchChapterList(manga, 1) - } else { - Observable.error(Exception("Licensed - No chapters to show")) + override fun searchMangaParse(response: Response): MangasPage { + val result = jsonParser.parse(response.body()!!.string()).obj + + // If "series" have boolean false value, then it doesn't have results. + if (!result["series"]!!.isJsonArray) + return MangasPage(emptyList(), false) + + val searchMangas = result.getAsJsonArray("series")?.map { + searchMangaItemParse(it.obj) + } + + if (searchMangas != null) + return MangasPage(searchMangas, false) + + return MangasPage(emptyList(), false) } - } - private fun fetchChapterList(manga: SManga, page: Int, - pastChapters: List = emptyList()): Observable> { - val chapters = pastChapters.toMutableList() - return fetchChapterListPage(manga, page) - .flatMap { - chapters += it - if (it.isEmpty()) { - Observable.just(chapters) - } else { - fetchChapterList(manga, page + 1, chapters) - } - } - } - - private fun fetchChapterListPage(manga: SManga, page: Int): Observable> { - return client.newCall(chapterListRequest(manga, page)) - .asObservableSuccess() - .map { response -> - chapterListParse(response) - } - } - - override fun chapterListRequest(manga: SManga): Request { - return chapterListRequest(manga, 1) - } - - private fun chapterListRequest(manga: SManga, page: Int): Request { - val id = manga.url.substringAfterLast("/") - return GET("$baseUrl/series/chapters_list.json?page=$page&id_serie=$id", catalogHeaders) - } - - override fun chapterListParse(response: Response): List { - val result = jsonParser.parse(response.body()!!.string()).obj - - if (!result["chapters"]!!.isJsonArray) - return emptyList() - - return result.getAsJsonArray("chapters")?.map { - chapterListItemParse(it.obj) - } ?: emptyList() - } - - private fun chapterListItemParse(obj: JsonObject): SChapter { - val scan = obj["releases"]!!.asJsonObject!!.entrySet().first().value.obj - val cName = obj["chapter_name"]!!.asString - - return SChapter.create().apply { - name = if (cName == "") "Capítulo " + obj["number"]!!.asString else cName - date_upload = parseChapterDate(obj["date"].nullString) - scanlator = scan["scanlators"]!!.asJsonArray.get(0)!!.asJsonObject["name"].nullString - url = scan["link"]!!.nullString ?: "" - chapter_number = obj["number"]!!.asString.toFloatOrNull() ?: "1".toFloat() + private fun searchMangaItemParse(obj: JsonObject) = SManga.create().apply { + title = obj["name"].nullString ?: "" + thumbnail_url = obj["cover"].nullString + url = obj["link"].nullString ?: "" + author = obj["author"].nullString + artist = obj["artist"].nullString } - } - private fun parseChapterDate(date: String?) : Long { - return try { - SimpleDateFormat("dd/MM/yyyy", Locale.ENGLISH).parse(date).time - } catch (e: ParseException) { - 0L + override fun mangaDetailsParse(response: Response): SManga { + val document = response.asJsoup() + val isCompleted = document.select("div#series-data span.series-author i.complete-series").first() != null + val cGenre = document.select("div#series-data ul.tags li").joinToString { it!!.text() } + + val seriesAuthor = if (isCompleted) { + document.select("div#series-data span.series-author").first()!!.nextSibling().toString().substringBeforeLast("+") + } else { + document.select("div#series-data span.series-author").first()!!.text().substringBeforeLast("+") + } + + val authors = seriesAuthor.split("&") + .map { it.trim() } + + val cAuthor = authors.filter { !it.contains("(Arte)") } + .map { author -> + if (author.contains(", ")) { + val authorSplit = author.split(", ") + authorSplit[1] + " " + authorSplit[0] + } else { + author + } + } + + val cArtist = authors.filter { it.contains("(Arte)") } + .map { it.replace("\\(Arte\\)".toRegex(), "").trim() } + .map { author -> + if (author.contains(", ")) { + val authorSplit = author.split(", ") + authorSplit[1] + " " + authorSplit[0] + } else { + author + } + } + + // Check if the manga was removed by the publisher. + val cStatus = if (document.select("div.series-blocked-img").first() == null) { + if (isCompleted) SManga.COMPLETED else SManga.ONGOING + } else { + SManga.LICENSED + } + + return SManga.create().apply { + genre = cGenre + status = cStatus + description = document.select("div#series-data span.series-desc").first()?.text() + author = cAuthor.joinToString("; ") + artist = if (cArtist.isEmpty()) cAuthor.joinToString("; ") else cArtist.joinToString("; ") + } } - } - override fun fetchPageList(chapter: SChapter): Observable> { - return client.newCall(pageListRequest(chapter)) - .asObservableSuccess() - .flatMap { response -> - val token = getReaderToken(response) - return@flatMap if (token == "") - Observable.error(Exception("Licensed - No chapter to show")) - else fetchPageListApi(chapter, token) - } - } - - private fun fetchPageListApi(chapter: SChapter, token: String): Observable> { - val id = "\\/(\\d+)\\/capitulo".toRegex().find(chapter.url)?.groupValues?.get(1) ?: "" - return client.newCall(pageListApiRequest(id, token)) - .asObservableSuccess() - .map { response -> - pageListParse(response) - } - } - - private fun pageListApiRequest(id: String, token: String): Request { - return GET("$baseUrl/leitor/pages/$id.json?key=$token", catalogHeaders) - } - - override fun pageListParse(response: Response): List { - val result = jsonParser.parse(response.body()!!.string()).obj - - return result["images"]!!.asJsonArray - .mapIndexed { i, obj -> - Page(i, obj.asString, obj.asString) - } - } - - override fun fetchImageUrl(page: Page): Observable { - return Observable.just(page.imageUrl!!) - } - - override fun imageUrlParse(response: Response): String = "" - - private fun getReaderToken(response: Response): String { - val document = response.asJsoup() - // The pages API needs the token provided in the reader script. - val scriptSrc = document.select("script[src*=\"reader.min.js\"]")?.first()?.attr("src") ?: "" - return "token=(.*)&id".toRegex().find(scriptSrc)?.groupValues?.get(1) ?: "" - } - - companion object { - val jsonParser by lazy { - JsonParser() + // Need to override because the chapter API is paginated. + // Adapted from: + // https://stackoverflow.com/questions/35254323/rxjs-observable-pagination + // https://stackoverflow.com/questions/40529232/angular-2-http-observables-and-recursive-requests + override fun fetchChapterList(manga: SManga): Observable> { + return if (manga.status != SManga.LICENSED) { + fetchChapterList(manga, 1) + } else { + Observable.error(Exception("Licensed - No chapters to show")) + } } - } -} \ No newline at end of file + + private fun fetchChapterList(manga: SManga, page: Int, + pastChapters: List = emptyList()): Observable> { + val chapters = pastChapters.toMutableList() + return fetchChapterListPage(manga, page) + .flatMap { + chapters += it + if (it.isEmpty()) { + Observable.just(chapters) + } else { + fetchChapterList(manga, page + 1, chapters) + } + } + } + + private fun fetchChapterListPage(manga: SManga, page: Int): Observable> { + return client.newCall(chapterListRequest(manga, page)) + .asObservableSuccess() + .map { response -> + chapterListParse(response) + } + } + + override fun chapterListRequest(manga: SManga): Request { + return chapterListRequest(manga, 1) + } + + private fun chapterListRequest(manga: SManga, page: Int): Request { + val id = manga.url.substringAfterLast("/") + return GET("$baseUrl/series/chapters_list.json?page=$page&id_serie=$id", catalogHeaders) + } + + override fun chapterListParse(response: Response): List { + val result = jsonParser.parse(response.body()!!.string()).obj + + if (!result["chapters"]!!.isJsonArray) + return emptyList() + + return result.getAsJsonArray("chapters")?.map { + chapterListItemParse(it.obj) + } ?: emptyList() + } + + private fun chapterListItemParse(obj: JsonObject): SChapter { + val scan = obj["releases"]!!.asJsonObject!!.entrySet().first().value.obj + val cName = obj["chapter_name"]!!.asString + + return SChapter.create().apply { + name = "Cap. ${obj["number"]!!.asString}" + (if (cName == "") "" else " - $cName") + date_upload = parseChapterDate(obj["date"].nullString) + scanlator = scan["scanlators"]!!.asJsonArray.get(0)!!.asJsonObject["name"].nullString + url = scan["link"]!!.nullString ?: "" + chapter_number = obj["number"]!!.asString.toFloatOrNull() ?: "1".toFloat() + } + } + + private fun parseChapterDate(date: String?) : Long { + return try { + SimpleDateFormat("dd/MM/yy", Locale.ENGLISH).parse(date).time + } catch (e: ParseException) { + 0L + } + } + + override fun fetchPageList(chapter: SChapter): Observable> { + return client.newCall(pageListRequest(chapter)) + .asObservableSuccess() + .flatMap { response -> + val token = getReaderToken(response) + return@flatMap if (token == "") + Observable.error(Exception("Licensed - No chapter to show")) + else fetchPageListApi(chapter, token) + } + } + + private fun fetchPageListApi(chapter: SChapter, token: String): Observable> { + val id = "\\/(\\d+)\\/capitulo".toRegex().find(chapter.url)?.groupValues?.get(1) ?: "" + return client.newCall(pageListApiRequest(id, token)) + .asObservableSuccess() + .map { response -> + pageListParse(response) + } + } + + private fun pageListApiRequest(id: String, token: String): Request { + return GET("$baseUrl/leitor/pages/$id.json?key=$token", catalogHeaders) + } + + override fun pageListParse(response: Response): List { + val result = jsonParser.parse(response.body()!!.string()).obj + + return result["images"]!!.asJsonArray + .mapIndexed { i, obj -> + Page(i, obj.asString, obj.asString) + } + } + + override fun fetchImageUrl(page: Page): Observable { + return Observable.just(page.imageUrl!!) + } + + override fun imageUrlParse(response: Response): String = "" + + private fun getReaderToken(response: Response): String { + val document = response.asJsoup() + // The pages API needs the token provided in the reader script. + val scriptSrc = document.select("script[src*=\"reader.min.js\"]")?.first()?.attr("src") ?: "" + return "token=(.*)&id".toRegex().find(scriptSrc)?.groupValues?.get(1) ?: "" + } + + companion object { + val jsonParser by lazy { + JsonParser() + } + } +} diff --git a/src/pt/unionmangas/build.gradle b/src/pt/unionmangas/build.gradle index 638740acb..ddec1d7a6 100644 --- a/src/pt/unionmangas/build.gradle +++ b/src/pt/unionmangas/build.gradle @@ -5,7 +5,7 @@ ext { appName = 'Tachiyomi: Union Mangás' pkgNameSuffix = 'pt.unionmangas' extClass = '.UnionMangas' - extVersionCode = 4 + extVersionCode = 5 libVersion = '1.2' } diff --git a/src/pt/unionmangas/src/eu/kanade/tachiyomi/extension/pt/unionmangas/UnionMangas.kt b/src/pt/unionmangas/src/eu/kanade/tachiyomi/extension/pt/unionmangas/UnionMangas.kt index 48cfb2f0b..926b70633 100644 --- a/src/pt/unionmangas/src/eu/kanade/tachiyomi/extension/pt/unionmangas/UnionMangas.kt +++ b/src/pt/unionmangas/src/eu/kanade/tachiyomi/extension/pt/unionmangas/UnionMangas.kt @@ -21,21 +21,19 @@ class UnionMangas : ParsedHttpSource() { override val name = "Union Mangás" - override val baseUrl = "http://unionmangas.top" + override val baseUrl = "https://unionmangas.top" override val lang = "pt" override val supportsLatest = true - // Sometimes the site it's very slow... + // Sometimes the site is very slow. override val client = - network.client.newBuilder() - .connectTimeout(3, TimeUnit.MINUTES) - .readTimeout(3, TimeUnit.MINUTES) - .writeTimeout(3, TimeUnit.MINUTES) - .build() - - private val langRegex: String = "( )?\\(Pt-Br\\)" + network.client.newBuilder() + .connectTimeout(3, TimeUnit.MINUTES) + .readTimeout(3, TimeUnit.MINUTES) + .writeTimeout(3, TimeUnit.MINUTES) + .build() private val catalogHeaders = Headers.Builder().apply { add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)") @@ -55,7 +53,7 @@ class UnionMangas : ParsedHttpSource() { manga.thumbnail_url = element.select("a img").first()?.attr("src") element.select("a").last().let { manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.text().replace(langRegex.toRegex(), "") + manga.title = it.text().replace(LANG_REGEX.toRegex(), "") } return manga @@ -63,7 +61,7 @@ class UnionMangas : ParsedHttpSource() { override fun popularMangaNextPageSelector() = ".pagination li:contains(Next)" - override fun latestUpdatesSelector() = "div.row[style=margin-bottom: 10px;] > div.col-md-12" + override fun latestUpdatesSelector() = "div.row[style] div.col-md-12[style]" override fun latestUpdatesRequest(page: Int): Request { val form = FormBody.Builder().apply { @@ -74,24 +72,33 @@ class UnionMangas : ParsedHttpSource() { } override fun latestUpdatesFromElement(element: Element): SManga { - return popularMangaFromElement(element) + // return popularMangaFromElement(element) + val manga = SManga.create() + val infoElements = element.select("a.link-titulo") + manga.thumbnail_url = infoElements.first()?.select("img")?.attr("src") + infoElements.last().let { + manga.setUrlWithoutDomain(it.attr("href")) + manga.title = it.text().replace(LANG_REGEX.toRegex(), "") + } + + return manga } override fun latestUpdatesNextPageSelector() = "div#linha-botao-mais" override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return GET("$baseUrl/busca/$query/$page", headers) + return GET("$baseUrl/busca/$query/$page", headers) } override fun searchMangaSelector() = ".bloco-manga" override fun searchMangaFromElement(element: Element): SManga { val manga = SManga.create() - - manga.thumbnail_url = element.select("a img.img-thumbnail").first().attr("src") + val thumbnailElement = element.select("a img.img-thumbnail").first() + manga.thumbnail_url = thumbnailElement.attr("src").replace("com.br", "top") element.select("a").last().let { manga.setUrlWithoutDomain(it.attr("href")) - manga.title = it.text().replace(langRegex.toRegex(), "") + manga.title = it.text().replace(LANG_REGEX.toRegex(), "") } return manga @@ -119,7 +126,7 @@ class UnionMangas : ParsedHttpSource() { manga.thumbnail_url = infoElement.select(".img-thumbnail").first()?.attr("src") // Need to grab title again because the ellipsize in search. - manga.title = infoElement.select("h2").first()!!.text().replace(langRegex.toRegex(), "") + manga.title = infoElement.select("h2").first()!!.text().replace(LANG_REGEX.toRegex(), "") return manga } @@ -166,4 +173,8 @@ class UnionMangas : ParsedHttpSource() { } override fun imageUrlParse(document: Document) = "" + + companion object { + private const val LANG_REGEX = "( )?\\(Pt-Br\\)" + } }