Change TsukiMangas API to v2 (#5795)
* Switch TsukiMangas API to v2. * Fix wrong condition in chapter pagination while loop. * Change select word in filter.
This commit is contained in:
parent
2004ad5d6e
commit
f47cba6bd9
|
@ -5,7 +5,7 @@ ext {
|
||||||
extName = 'Tsuki Mangás'
|
extName = 'Tsuki Mangás'
|
||||||
pkgNameSuffix = 'pt.tsukimangas'
|
pkgNameSuffix = 'pt.tsukimangas'
|
||||||
extClass = '.TsukiMangas'
|
extClass = '.TsukiMangas'
|
||||||
extVersionCode = 8
|
extVersionCode = 9
|
||||||
libVersion = '1.2'
|
libVersion = '1.2'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ class TsukiMangas : HttpSource() {
|
||||||
|
|
||||||
override val supportsLatest = true
|
override val supportsLatest = true
|
||||||
|
|
||||||
private val rateLimitInterceptor = RateLimitInterceptor(150, 1, TimeUnit.MINUTES)
|
private val rateLimitInterceptor = RateLimitInterceptor(100, 1, TimeUnit.MINUTES)
|
||||||
|
|
||||||
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
||||||
.addInterceptor(rateLimitInterceptor)
|
.addInterceptor(rateLimitInterceptor)
|
||||||
|
@ -51,102 +51,94 @@ class TsukiMangas : HttpSource() {
|
||||||
.add("Referer", baseUrl)
|
.add("Referer", baseUrl)
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int): Request {
|
override fun popularMangaRequest(page: Int): Request {
|
||||||
return GET("$baseUrl/api/melhores", headers)
|
return GET("$baseUrl/api/v2/home", headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun popularMangaParse(response: Response): MangasPage {
|
override fun popularMangaParse(response: Response): MangasPage {
|
||||||
val result = response.asJson().array
|
val result = response.asJson().obj
|
||||||
|
|
||||||
val popularMangas = result.map { popularMangaItemParse(it.obj) }
|
val popularMangas = result["slides"].array
|
||||||
|
.map { popularMangaItemParse(it.obj) }
|
||||||
|
|
||||||
return MangasPage(popularMangas, false)
|
return MangasPage(popularMangas, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun popularMangaItemParse(obj: JsonObject) = SManga.create().apply {
|
private fun popularMangaItemParse(obj: JsonObject) = SManga.create().apply {
|
||||||
title = obj["TITULO"].string
|
title = obj["title"].string
|
||||||
thumbnail_url = baseUrl + "/imgs/" + obj["CAPA"].string.substringBefore("?")
|
thumbnail_url = baseUrl + "/imgs/" + obj["poster"].string.substringBefore("?")
|
||||||
url = "/obra/${obj["ID"].int}/${obj["URL"].string}"
|
url = "/obra/${obj["id"].int}/${obj["url"].string}"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun latestUpdatesRequest(page: Int): Request {
|
override fun latestUpdatesRequest(page: Int): Request {
|
||||||
return GET("$baseUrl/api/lancamentos/$page", headers)
|
return GET("$baseUrl/api/v2/home/lastests?page=$page", headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun latestUpdatesParse(response: Response): MangasPage {
|
override fun latestUpdatesParse(response: Response): MangasPage {
|
||||||
val json = response.asJson().array
|
val result = response.asJson().obj
|
||||||
|
|
||||||
if (json.size() == 0)
|
val latestMangas = result["data"].array
|
||||||
return MangasPage(emptyList(), false)
|
|
||||||
|
|
||||||
val result = json[0].obj
|
|
||||||
|
|
||||||
val latestMangas = result["mangas"].array
|
|
||||||
.map { latestMangaItemParse(it.obj) }
|
.map { latestMangaItemParse(it.obj) }
|
||||||
|
|
||||||
// Latest pagination doesn't seen to have a lower end.
|
val hasNextPage = result["page"].int < result["lastPage"].int
|
||||||
return MangasPage(latestMangas, true)
|
return MangasPage(latestMangas, hasNextPage)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun latestMangaItemParse(obj: JsonObject) = SManga.create().apply {
|
private fun latestMangaItemParse(obj: JsonObject) = SManga.create().apply {
|
||||||
title = obj["TITULO"].string
|
title = obj["title"].string
|
||||||
thumbnail_url = baseUrl + "/imgs/" + obj["CAPA"].string
|
thumbnail_url = baseUrl + "/imgs/" + obj["poster"].string.substringBefore("?")
|
||||||
url = "/obra/${obj["ID"].int}/${obj["URL"].string}"
|
url = "/obra/${obj["id"].int}/${obj["url"].string}"
|
||||||
}
|
|
||||||
|
|
||||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
|
||||||
return client.newCall(searchMangaRequest(page, query, filters))
|
|
||||||
.asObservableSuccess()
|
|
||||||
.map { response -> searchMangaParse(response) }
|
|
||||||
.onErrorReturn {
|
|
||||||
if (it.message!!.contains("404")) {
|
|
||||||
return@onErrorReturn MangasPage(emptyList(), false)
|
|
||||||
}
|
|
||||||
|
|
||||||
throw it
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
val newHeaders = headersBuilder()
|
val newHeaders = headersBuilder()
|
||||||
.set("Referer", "$baseUrl/lista-mangas")
|
.set("Referer", "$baseUrl/lista-completa")
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val pathQuery = if (query.isEmpty()) "all" else query
|
val url = HttpUrl.parse("$baseUrl/api/v2/mangas?page=$page")!!.newBuilder()
|
||||||
|
url.addQueryParameter("title", query)
|
||||||
|
|
||||||
val genreFilter = if (filters.isEmpty()) null else filters[0] as GenreFilter
|
filters.forEach { filter ->
|
||||||
val genreQuery = genreFilter?.state
|
when (filter) {
|
||||||
?.filter { it.state }
|
is GenreFilter -> {
|
||||||
?.joinToString(",") { it.name } ?: "all"
|
filter.state
|
||||||
|
.filter { it.state }
|
||||||
|
.forEach { url.addQueryParameter("genres", it.name) }
|
||||||
|
}
|
||||||
|
|
||||||
val url = HttpUrl.parse("$baseUrl/api/generos")!!.newBuilder()
|
is TypeFilter -> {
|
||||||
.addEncodedPathSegment(genreQuery)
|
if (filter.state > 0) {
|
||||||
.addPathSegment(page.toString())
|
url.addQueryParameter("format", filter.state.toString())
|
||||||
.addEncodedPathSegment(pathQuery)
|
}
|
||||||
.addPathSegment("all")
|
}
|
||||||
.toString()
|
|
||||||
|
|
||||||
return GET(url, newHeaders)
|
is AdultFilter -> {
|
||||||
|
if (filter.state == Filter.TriState.STATE_INCLUDE) {
|
||||||
|
url.addQueryParameter("adult_content", "1")
|
||||||
|
} else if (filter.state == Filter.TriState.STATE_EXCLUDE) {
|
||||||
|
url.addQueryParameter("adult_content", "false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return GET(url.toString(), newHeaders)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun searchMangaParse(response: Response): MangasPage {
|
override fun searchMangaParse(response: Response): MangasPage {
|
||||||
val result = response.asJson().array
|
val result = response.asJson().obj
|
||||||
|
|
||||||
if (result.size() == 0)
|
val searchResults = result["data"].array
|
||||||
return MangasPage(emptyList(), false)
|
.map { searchMangaItemParse(it.obj) }
|
||||||
|
|
||||||
val searchMangas = result.map { searchMangaItemParse(it.obj) }
|
val hasNextPage = result["page"].int < result["lastPage"].int
|
||||||
|
|
||||||
val currentPage = response.request().url().pathSegments()[3].toInt()
|
return MangasPage(searchResults, hasNextPage)
|
||||||
val lastPage = result[0].obj["page"].array[0].int
|
|
||||||
val hasNextPage = currentPage < lastPage
|
|
||||||
|
|
||||||
return MangasPage(searchMangas, hasNextPage)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun searchMangaItemParse(obj: JsonObject) = SManga.create().apply {
|
private fun searchMangaItemParse(obj: JsonObject) = SManga.create().apply {
|
||||||
title = obj["TITULO"].string
|
title = obj["title"].string
|
||||||
thumbnail_url = baseUrl + "/imgs/" + obj["CAPA"].string.substringBefore("?")
|
thumbnail_url = baseUrl + "/imgs/" + obj["poster"].string.substringBefore("?")
|
||||||
url = "/obra/${obj["ID"].int}/${obj["URL"].string}"
|
url = "/obra/${obj["id"].int}/${obj["url"].string}"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Workaround to allow "Open in browser" use the real URL.
|
// Workaround to allow "Open in browser" use the real URL.
|
||||||
|
@ -165,7 +157,7 @@ class TsukiMangas : HttpSource() {
|
||||||
|
|
||||||
val mangaId = manga.url.substringAfter("obra/").substringBefore("/")
|
val mangaId = manga.url.substringAfter("obra/").substringBefore("/")
|
||||||
|
|
||||||
return GET("$baseUrl/api/mangas/$mangaId", newHeaders)
|
return GET("$baseUrl/api/v2/mangas/$mangaId", newHeaders)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun mangaDetailsRequest(manga: SManga): Request {
|
override fun mangaDetailsRequest(manga: SManga): Request {
|
||||||
|
@ -177,16 +169,16 @@ class TsukiMangas : HttpSource() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun mangaDetailsParse(response: Response): SManga {
|
override fun mangaDetailsParse(response: Response): SManga {
|
||||||
val result = response.asJson().obj["manga"].array[0].obj
|
val result = response.asJson().obj
|
||||||
|
|
||||||
return SManga.create().apply {
|
return SManga.create().apply {
|
||||||
title = result["TITULO"].string
|
title = result["title"].string
|
||||||
thumbnail_url = baseUrl + "/imgs/" + result["CAPA"].string.substringBefore("?")
|
thumbnail_url = baseUrl + "/imgs/" + result["poster"].string.substringBefore("?")
|
||||||
description = result["SINOPSE"].string
|
description = result["synopsis"].string
|
||||||
status = result["STATUS"].string.toStatus()
|
status = result["status"].string.toStatus()
|
||||||
author = result["AUTOR"].string
|
author = result["author"].string
|
||||||
artist = result["ARTISTA"].string
|
artist = result["artist"].string
|
||||||
genre = result["GENEROS"].string
|
genre = result["genres"].array.joinToString { it.obj["genre"].string }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,42 +191,48 @@ class TsukiMangas : HttpSource() {
|
||||||
.set("Referer", baseUrl + mangaUrl)
|
.set("Referer", baseUrl + mangaUrl)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
return GET("$baseUrl/api/capitulospag/$mangaId/DESC/$page", newHeaders)
|
return GET("$baseUrl/api/v2/chapters?manga_id=$mangaId&order=desc&page=$page", newHeaders)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun chapterListParse(response: Response): List<SChapter> {
|
override fun chapterListParse(response: Response): List<SChapter> {
|
||||||
var result = response.asJson().array
|
var result = response.asJson().obj
|
||||||
|
|
||||||
if (result.size() == 0)
|
|
||||||
return emptyList()
|
|
||||||
|
|
||||||
val mangaUrl = response.request().header("Referer")!!.substringAfter(baseUrl)
|
val mangaUrl = response.request().header("Referer")!!.substringAfter(baseUrl)
|
||||||
var page = 1
|
var page = 2
|
||||||
|
val lastPage = result["lastPage"].int
|
||||||
|
|
||||||
val chapters = mutableListOf<SChapter>()
|
val chapters = result["data"].array
|
||||||
|
.flatMap { chapterListItemParse(it.obj, mangaUrl) }
|
||||||
|
.toMutableList()
|
||||||
|
|
||||||
while (result.size() != 0) {
|
while (page <= lastPage) {
|
||||||
chapters += result
|
val newRequest = chapterListRequestPaginated(mangaUrl, page++)
|
||||||
.map { chapterListItemParse(it.obj, mangaUrl) }
|
result = client.newCall(newRequest).execute().asJson().obj
|
||||||
|
|
||||||
|
chapters += result["data"].array
|
||||||
|
.flatMap { chapterListItemParse(it.obj, mangaUrl) }
|
||||||
.toMutableList()
|
.toMutableList()
|
||||||
|
|
||||||
val newRequest = chapterListRequestPaginated(mangaUrl, ++page)
|
|
||||||
result = client.newCall(newRequest).execute().asJson().array
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return chapters
|
return chapters
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun chapterListItemParse(obj: JsonObject, mangaUrl: String): SChapter = SChapter.create().apply {
|
private fun chapterListItemParse(obj: JsonObject, mangaUrl: String): List<SChapter> {
|
||||||
val mangaId = mangaUrl.substringAfter("obra/").substringBefore("/")
|
val mangaId = mangaUrl.substringAfter("obra/").substringBefore("/")
|
||||||
val mangaSlug = mangaUrl.substringAfterLast("/")
|
val mangaSlug = mangaUrl.substringAfterLast("/")
|
||||||
|
|
||||||
name = "Cap. " + obj["NUMERO"].string +
|
return obj["versions"].array.map { version ->
|
||||||
(if (obj["TITULO"].string.isNotEmpty()) " - " + obj["TITULO"].string else "")
|
SChapter.create().apply {
|
||||||
chapter_number = obj["NUMERO"].string.toFloatOrNull() ?: -1f
|
name = "Cap. " + obj["number"].string +
|
||||||
scanlator = obj["scans"].array.joinToString { it.obj["NOME"].string }
|
(if (obj["title"].string.isNotEmpty()) " - " + obj["title"].string else "")
|
||||||
date_upload = obj["DATA"].string.substringBefore("T").toDate()
|
chapter_number = obj["number"].string.toFloatOrNull() ?: -1f
|
||||||
url = "/leitor/$mangaId/${obj["ID"].int}/$mangaSlug/${obj["NUMERO"].string}"
|
scanlator = version.obj["scans"].array
|
||||||
|
.sortedBy { it.obj["scan"].obj["name"].string }
|
||||||
|
.joinToString { it.obj["scan"].obj["name"].string }
|
||||||
|
date_upload = version.obj["created_at"].string.substringBefore(" ").toDate()
|
||||||
|
url = "/leitor/$mangaId/${version.obj["id"].int}/$mangaSlug/${obj["number"].string}"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun pageListRequest(chapter: SChapter): Request {
|
override fun pageListRequest(chapter: SChapter): Request {
|
||||||
|
@ -242,18 +240,22 @@ class TsukiMangas : HttpSource() {
|
||||||
.set("Referer", baseUrl + chapter.url)
|
.set("Referer", baseUrl + chapter.url)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val mangaId = chapter.url.substringAfter("leitor/").substringBefore("/")
|
val mangaId = chapter.url
|
||||||
val chapterId = chapter.url.substringAfter("$mangaId/").substringBefore("/")
|
.substringAfter("leitor/")
|
||||||
|
.substringBefore("/")
|
||||||
|
val versionId = chapter.url
|
||||||
|
.substringAfter("$mangaId/")
|
||||||
|
.substringBefore("/")
|
||||||
|
|
||||||
return GET("$baseUrl/api/leitor/$mangaId/$chapterId", newHeaders)
|
return GET("$baseUrl/api/v2/chapter/versions/$versionId", newHeaders)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun pageListParse(response: Response): List<Page> {
|
override fun pageListParse(response: Response): List<Page> {
|
||||||
val result = response.asJson().array
|
val result = response.asJson().obj
|
||||||
|
|
||||||
return result.mapIndexed { i, page ->
|
return result["pages"].array.mapIndexed { i, page ->
|
||||||
val cdnUrl = "https://cdn${page.obj["SERVIDOR"].string}.tsukimangas.com"
|
val cdnUrl = "https://cdn${page.obj["server"].string}.tsukimangas.com"
|
||||||
Page(i, "$baseUrl/", cdnUrl + page.obj["IMG"].string)
|
Page(i, "$baseUrl/", cdnUrl + page.obj["url"].string)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -275,9 +277,17 @@ class TsukiMangas : HttpSource() {
|
||||||
|
|
||||||
private class GenreFilter(genres: List<Genre>) : Filter.Group<Genre>("Gêneros", genres)
|
private class GenreFilter(genres: List<Genre>) : Filter.Group<Genre>("Gêneros", genres)
|
||||||
|
|
||||||
override fun getFilterList(): FilterList = FilterList(GenreFilter(getGenreList()))
|
private class TypeFilter(types: List<String>) : Filter.Select<String>("Formato", types.toTypedArray())
|
||||||
|
|
||||||
// [...document.querySelectorAll(".multiselect__element span span")]
|
private class AdultFilter : Filter.TriState("Conteúdo adulto")
|
||||||
|
|
||||||
|
override fun getFilterList(): FilterList = FilterList(
|
||||||
|
GenreFilter(getGenreList()),
|
||||||
|
TypeFilter(getSerieTypes()),
|
||||||
|
AdultFilter()
|
||||||
|
)
|
||||||
|
|
||||||
|
// [...document.querySelectorAll(".multiselect:first-of-type .multiselect__element span span")]
|
||||||
// .map(i => `Genre("${i.innerHTML}")`).join(",\n")
|
// .map(i => `Genre("${i.innerHTML}")`).join(",\n")
|
||||||
private fun getGenreList(): List<Genre> = listOf(
|
private fun getGenreList(): List<Genre> = listOf(
|
||||||
Genre("4-koma"),
|
Genre("4-koma"),
|
||||||
|
@ -331,6 +341,14 @@ class TsukiMangas : HttpSource() {
|
||||||
Genre("Zumbi")
|
Genre("Zumbi")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private fun getSerieTypes(): List<String> = listOf(
|
||||||
|
"Todos",
|
||||||
|
"Mangá",
|
||||||
|
"Manhwa",
|
||||||
|
"Manhua",
|
||||||
|
"Novel"
|
||||||
|
)
|
||||||
|
|
||||||
private fun String.toDate(): Long {
|
private fun String.toDate(): Long {
|
||||||
return try {
|
return try {
|
||||||
DATE_FORMATTER.parse(this)?.time ?: 0L
|
DATE_FORMATTER.parse(this)?.time ?: 0L
|
||||||
|
|
Loading…
Reference in New Issue