diff --git a/src/pt/mangalivre/build.gradle b/src/pt/mangalivre/build.gradle index 376385fe8..34af04a79 100644 --- a/src/pt/mangalivre/build.gradle +++ b/src/pt/mangalivre/build.gradle @@ -5,13 +5,8 @@ ext { appName = 'Tachiyomi: Mangá Livre' pkgNameSuffix = 'pt.mangalivre' extClass = '.MangaLivre' - extVersionCode = 3 + extVersionCode = 4 libVersion = '1.2' } -dependencies { - compileOnly 'com.google.code.gson:gson:2.8.2' - compileOnly 'com.github.salomonbrys.kotson:kotson:2.5.0' -} - apply from: "$rootDir/common.gradle" 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 2757d9f25..dc6694c9f 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 @@ -1,25 +1,9 @@ package eu.kanade.tachiyomi.extension.pt.mangalivre -import com.github.salomonbrys.kotson.nullString -import com.github.salomonbrys.kotson.obj -import com.google.gson.JsonObject -import com.google.gson.JsonParser -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.source.model.* import eu.kanade.tachiyomi.source.online.HttpSource -import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.FormBody -import okhttp3.Headers -import okhttp3.Request import okhttp3.Response -import rx.Observable import java.lang.Exception -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.* -import java.util.concurrent.TimeUnit class MangaLivre : HttpSource() { override val name = "MangaLivre" @@ -30,273 +14,18 @@ class MangaLivre : HttpSource() { 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() - - 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) - } - - val hasNextPage = response.request().url().queryParameter("page")!!.toInt() < 10 - - if (popularMangas != null) - return MangasPage(popularMangas, hasNextPage) - - return MangasPage(emptyList(), false) - } - - private fun popularMangaItemParse(obj: JsonObject) = SManga.create().apply { - title = obj["serie_name"].nullString ?: "" - thumbnail_url = obj["cover"].nullString - url = obj["link"].nullString ?: "" - } - - override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/home/releases?page=$page", catalogHeaders) - } - - 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) - } - - 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) - } - - 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) - } - - if (searchMangas != null) - return MangasPage(searchMangas, false) - - return MangasPage(emptyList(), false) - } - - 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 - } - - 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 = document.select("div#series-data span.series-author").text() - .substringAfter("Completo").substringBefore("+") - - val authors = seriesAuthor.split("&") - .map { it.trim() } - - val cAuthor = authors.filter { !it.contains("(Arte)") } - .map { it.split(", ").reversed().joinToString(" ") } - - val cArtist = authors.filter { it.contains("(Arte)") } - .map { it.replace("\\(Arte\\)".toRegex(), "").trim() } - .map { it.split(", ").reversed().joinToString(" ") } - - // Check if the manga was removed by the publisher. - var seriesBlocked = document.select("div.series-blocked-img").first() - val cStatus = when { - seriesBlocked == null && isCompleted -> SManga.COMPLETED - seriesBlocked == null && !isCompleted -> 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("; ") - } - } - - // 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")) - } - } - - 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 - - val scanlators = scan["scanlators"]!!.asJsonArray - .joinToString { it.asJsonObject["name"].asString } - - return SChapter.create().apply { - name = "Cap. ${obj["number"]!!.asString}" + (if (cName == "") "" else " - $cName") - date_upload = parseChapterDate(obj["date_created"].asString.substring(0, 10)) - scanlator = scanlators - url = scan["link"]!!.nullString ?: "" - chapter_number = obj["number"]!!.asString.toFloatOrNull() ?: "1".toFloat() - } - } - - private fun parseChapterDate(date: String?) : Long { - return try { - SimpleDateFormat("yyyy-MM-dd", 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) ?: "" - } + override fun popularMangaRequest(page: Int) = throw Exception(NEED_MIGRATION) + override fun popularMangaParse(response: Response) = throw Exception(NEED_MIGRATION) + override fun latestUpdatesRequest(page: Int) = throw Exception(NEED_MIGRATION) + override fun latestUpdatesParse(response: Response) = throw Exception(NEED_MIGRATION) + override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw Exception(NEED_MIGRATION) + override fun searchMangaParse(response: Response) = throw Exception(NEED_MIGRATION) + override fun mangaDetailsParse(response: Response) = throw Exception(NEED_MIGRATION) + override fun chapterListParse(response: Response) = throw Exception(NEED_MIGRATION) + override fun pageListParse(response: Response) = throw Exception(NEED_MIGRATION) + override fun imageUrlParse(response: Response) = throw Exception(NEED_MIGRATION) companion object { - val jsonParser by lazy { - JsonParser() - } + private const val NEED_MIGRATION = "Catálogo incorporado na nova versão da extensão mangásPROJECT." } } diff --git a/src/pt/mangasproject/build.gradle b/src/pt/mangasproject/build.gradle index 208d9f2a3..093615b3b 100644 --- a/src/pt/mangasproject/build.gradle +++ b/src/pt/mangasproject/build.gradle @@ -4,8 +4,8 @@ apply plugin: 'kotlin-android' ext { appName = 'Tachiyomi: mangásPROJECT' pkgNameSuffix = 'pt.mangasproject' - extClass = '.MangasProject' - extVersionCode = 3 + extClass = '.MangasProjectFactory' + extVersionCode = 4 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 3d2a61392..d86781c9b 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 @@ -1,19 +1,17 @@ package eu.kanade.tachiyomi.extension.pt.mangasproject -import com.github.salomonbrys.kotson.nullString +import com.github.salomonbrys.kotson.array import com.github.salomonbrys.kotson.obj +import com.github.salomonbrys.kotson.string import com.google.gson.JsonObject import com.google.gson.JsonParser import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.source.model.* import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.util.asJsoup -import okhttp3.FormBody -import okhttp3.Headers -import okhttp3.Request -import okhttp3.Response +import okhttp3.* +import org.jsoup.nodes.Element import rx.Observable import java.lang.Exception import java.text.ParseException @@ -21,222 +19,217 @@ import java.text.SimpleDateFormat import java.util.* import java.util.concurrent.TimeUnit -class MangasProject : HttpSource() { - override val name = "mangásPROJECT" - - override val baseUrl = "https://leitor.net" - +abstract class MangasProject(override val name: String, + override val baseUrl: String) : HttpSource() { override val lang = "pt" 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() + override val client = network.client.newBuilder() + .connectTimeout(1, TimeUnit.MINUTES) + .readTimeout(1, TimeUnit.MINUTES) + .writeTimeout(1, TimeUnit.MINUTES) + .addInterceptor { pageListIntercept(it) } + .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 headersBuilder(): Headers.Builder = Headers.Builder() + .add("User-Agent", USER_AGENT) + .add("Referer", baseUrl) + .add("X-Requested-With", "XMLHttpRequest") override fun popularMangaRequest(page: Int): Request { - return GET("$baseUrl/home/most_read?page=$page", catalogHeaders) + return GET("$baseUrl/home/most_read?page=$page", headers) } override fun popularMangaParse(response: Response): MangasPage { - val result = jsonParser.parse(response.body()!!.string()).obj + val result = response.asJsonObject() // 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) - } + val popularMangas = result["most_read"].array + .map { popularMangaItemParse(it.obj) } - val hasNextPage = response.request().url().queryParameter("page")!!.toInt() < 10 + val page = response.request().url().queryParameter("page")!!.toInt() - if (popularMangas != null) - return MangasPage(popularMangas, hasNextPage) - - return MangasPage(emptyList(), false) + return MangasPage(popularMangas, page < 10) } private fun popularMangaItemParse(obj: JsonObject) = SManga.create().apply { - title = obj["serie_name"].nullString ?: "" - thumbnail_url = obj["cover"].nullString - url = obj["link"].nullString ?: "" + title = obj["serie_name"].string + thumbnail_url = obj["cover"].string + url = obj["link"].string } override fun latestUpdatesRequest(page: Int): Request { - return GET("$baseUrl/home/releases?page=$page", catalogHeaders) + return GET("$baseUrl/home/releases?page=$page", headers) } override fun latestUpdatesParse(response: Response): MangasPage { if (response.code() == 500) return MangasPage(emptyList(), false) - val result = jsonParser.parse(response.body()!!.string()).obj + val result = response.asJsonObject() - val latestMangas = result.getAsJsonArray("releases")?.map { - latestMangaItemParse(it.obj) - } + val latestMangas = result["releases"].array + .map { latestMangaItemParse(it.obj) } - val hasNextPage = response.request().url().queryParameter("page")!!.toInt() < 5 + val page = response.request().url().queryParameter("page")!!.toInt() - if (latestMangas != null) - return MangasPage(latestMangas, hasNextPage) - - return MangasPage(emptyList(), false) + return MangasPage(latestMangas, page < 5) } private fun latestMangaItemParse(obj: JsonObject) = SManga.create().apply { - title = obj["name"].nullString ?: "" - thumbnail_url = obj["image"].nullString - url = obj["link"].nullString ?: "" + title = obj["name"].string + thumbnail_url = obj["image"].string + url = obj["link"].string } override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val form = FormBody.Builder().apply { - add("search", query) - } + val form = FormBody.Builder() + .add("search", query) + .build() - return POST("$baseUrl/lib/search/series.json", catalogHeaders, form.build()) + return POST("$baseUrl/lib/search/series.json", headers, form) } override fun searchMangaParse(response: Response): MangasPage { - val result = jsonParser.parse(response.body()!!.string()).obj + val result = response.asJsonObject() // 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) - } + val searchMangas = result["series"].array + .map { searchMangaItemParse(it.obj) } - if (searchMangas != null) - return MangasPage(searchMangas, false) - - return MangasPage(emptyList(), false) + return MangasPage(searchMangas, false) } 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 + title = obj["name"].string + thumbnail_url = obj["cover"].string + url = obj["link"].string + author = obj["author"].string + artist = obj["artist"].string + } + + override fun mangaDetailsRequest(manga: SManga): Request { + val newHeaders = Headers.Builder() + .add("User-Agent", USER_AGENT) + .add("Referer", baseUrl) + .build() + + return GET(baseUrl + manga.url, newHeaders) } 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 = document.select("div#series-data span.series-author").text() - .substringAfter("Completo").substringBefore("+") + val seriesData = document.select("#series-data") - val authors = seriesAuthor.split("&") - .map { it.trim() } - - val cAuthor = authors.filter { !it.contains("(Arte)") } - .map { it.split(", ").reversed().joinToString(" ") } - - val cArtist = authors.filter { it.contains("(Arte)") } - .map { it.replace("\\(Arte\\)".toRegex(), "").trim() } - .map { it.split(", ").reversed().joinToString(" ") } + val isCompleted = seriesData.select("span.series-author i.complete-series").first() != null // Check if the manga was removed by the publisher. - var seriesBlocked = document.select("div.series-blocked-img").first() - val cStatus = when { - seriesBlocked == null && isCompleted -> SManga.COMPLETED - seriesBlocked == null && !isCompleted -> SManga.ONGOING - else -> SManga.LICENSED - } + val seriesBlocked = document.select("div.series-blocked-img").first() + + val seriesAuthors = document.select("div#series-data span.series-author").text() + .substringAfter("Completo") + .substringBefore("+") + .split("&") + .map { it.trim() } + + val seriesAuthor = seriesAuthors + .filter { !it.contains("(Arte)") } + .joinToString("; ") { + it.split(", ") + .reversed() + .joinToString(" ") + } 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("; ") + thumbnail_url = seriesData.select("div.series-img > div.cover > img").attr("src") + description = seriesData.select("span.series-desc").text() + + status = parseStatus(seriesBlocked, isCompleted) + author = seriesAuthor + artist = seriesAuthors.filter { it.contains("(Arte)") } + .map { it.replace("\\(Arte\\)".toRegex(), "").trim() } + .joinToString("; ") { + it.split(", ") + .reversed() + .joinToString(" ") + } + .ifEmpty { seriesAuthor } + genre = seriesData.select("div#series-data ul.tags li") + .joinToString { it.text() } } } - // 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 + private fun parseStatus(seriesBlocked: Element?, isCompleted: Boolean) = when { + seriesBlocked != null -> SManga.LICENSED + isCompleted -> SManga.COMPLETED + else -> SManga.ONGOING + } + override fun fetchChapterList(manga: SManga): Observable> { - return if (manga.status != SManga.LICENSED) { - fetchChapterList(manga, 1) - } else { - Observable.error(Exception("Licensed - No chapters to show")) - } - } + if (manga.status != SManga.LICENSED) + return super.fetchChapterList(manga) - 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) - } + return Observable.error(Exception("Mangá licenciado e removido pela editora.")) } override fun chapterListRequest(manga: SManga): Request { - return chapterListRequest(manga, 1) + val id = manga.url.substringAfterLast("/") + + return chapterListRequestPaginated(manga.url, id, 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) + private fun chapterListRequestPaginated(mangaUrl: String, id: String, page: Int): Request { + val newHeaders = headersBuilder() + .set("Referer", baseUrl + mangaUrl) + .build() + + return GET("$baseUrl/series/chapters_list.json?page=$page&id_serie=$id", newHeaders) } override fun chapterListParse(response: Response): List { - val result = jsonParser.parse(response.body()!!.string()).obj + var result = response.asJsonObject() if (!result["chapters"]!!.isJsonArray) return emptyList() - return result.getAsJsonArray("chapters")?.map { - chapterListItemParse(it.obj) - } ?: emptyList() + val mangaUrl = response.request().header("Referer")!! + val mangaId = mangaUrl.substringAfterLast("/") + var page = 1 + + val chapters = mutableListOf() + + while (result["chapters"]!!.isJsonArray) { + chapters += result["chapters"].array + .map { chapterListItemParse(it.obj) } + .toMutableList() + + val newRequest = chapterListRequestPaginated(mangaUrl, mangaId, ++page) + result = client.newCall(newRequest).execute().asJsonObject() + } + + return chapters } private fun chapterListItemParse(obj: JsonObject): SChapter { - val scan = obj["releases"]!!.asJsonObject!!.entrySet().first().value.obj - val cName = obj["chapter_name"]!!.asString - - val scanlators = scan["scanlators"]!!.asJsonArray - .joinToString { it.asJsonObject["name"].asString } + val scan = obj["releases"].obj.entrySet().first().value.obj + val chapterName = obj["chapter_name"]!!.string return SChapter.create().apply { - name = "Cap. ${obj["number"]!!.asString}" + (if (cName == "") "" else " - $cName") - date_upload = parseChapterDate(obj["date_created"].asString.substring(0, 10)) - scanlator = scanlators - url = scan["link"]!!.nullString ?: "" - chapter_number = obj["number"]!!.asString.toFloatOrNull() ?: "1".toFloat() + name = "Cap. ${obj["number"].string}" + (if (chapterName == "") "" else " - $chapterName") + date_upload = parseChapterDate(obj["date_created"].string.substringBefore("T")) + scanlator = scan["scanlators"]!!.array + .joinToString { it.obj["name"].string } + url = scan["link"].string + chapter_number = obj["number"].string.toFloatOrNull() ?: 0f } } @@ -248,37 +241,52 @@ class MangasProject : HttpSource() { } } - 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) - } + override fun pageListRequest(chapter: SChapter): Request { + val newHeaders = Headers.Builder() + .add("User-Agent", USER_AGENT) + .add("Referer", baseUrl + chapter.url) + .build() + + return GET(baseUrl + chapter.url, newHeaders) } - 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(chapterUrl: String, token: String): Request { + val newHeaders = headersBuilder() + .set("Referer", chapterUrl) + .build() + + val id = chapterUrl + .substringBeforeLast("/") + .substringAfterLast("/") + + return GET("$baseUrl/leitor/pages/$id.json?key=$token", newHeaders) } - private fun pageListApiRequest(id: String, token: String): Request { - return GET("$baseUrl/leitor/pages/$id.json?key=$token", catalogHeaders) + private fun pageListIntercept(chain: Interceptor.Chain): Response { + val request = chain.request() + val result = chain.proceed(request) + + if (!request.url().toString().contains("capitulo-")) + return result + + val document = result.asJsoup() + val readerSrc = document.select("script[src*=\"reader.min.js\"]") + ?.attr("src") ?: "" + + val token = TOKEN_REGEX.find(readerSrc)?.groupValues?.get(1) ?: "" + + if (token.isEmpty()) + throw Exception("Mangá licenciado e removido pela editora.") + + return chain.proceed(pageListApiRequest(request.url().toString(), token)) } override fun pageListParse(response: Response): List { - val result = jsonParser.parse(response.body()!!.string()).obj + val result = response.asJsonObject() - return result["images"]!!.asJsonArray - .mapIndexed { i, obj -> - Page(i, obj.asString, obj.asString) - } + return result["images"].array + .filter { it.string.startsWith("http") } + .mapIndexed { i, obj -> Page(i, "", obj.string)} } override fun fetchImageUrl(page: Page): Observable { @@ -287,16 +295,13 @@ class MangasProject : HttpSource() { 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) ?: "" - } + private fun Response.asJsonObject(): JsonObject = JSON_PARSER.parse(body()!!.string()).obj companion object { - val jsonParser by lazy { - JsonParser() - } + private const val 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" + + private val TOKEN_REGEX = "token=(.*)&id".toRegex() + + private val JSON_PARSER by lazy { JsonParser() } } } diff --git a/src/pt/mangasproject/src/eu/kanade/tachiyomi/extension/pt/mangasproject/MangasProjectFactory.kt b/src/pt/mangasproject/src/eu/kanade/tachiyomi/extension/pt/mangasproject/MangasProjectFactory.kt new file mode 100644 index 000000000..0b916d2c7 --- /dev/null +++ b/src/pt/mangasproject/src/eu/kanade/tachiyomi/extension/pt/mangasproject/MangasProjectFactory.kt @@ -0,0 +1,16 @@ +package eu.kanade.tachiyomi.extension.pt.mangasproject + +import eu.kanade.tachiyomi.source.Source +import eu.kanade.tachiyomi.source.SourceFactory + +class MangasProjectFactory : SourceFactory { + override fun createSources(): List = getAllSources() +} + +class MangasProjectOriginal : MangasProject("mangásPROJECT", "https://leitor.net") +class MangaLivre : MangasProject("MangaLivre", "https://mangalivre.com") + +fun getAllSources(): List = listOf( + MangasProjectOriginal(), + MangaLivre() +)