Union Manga: bugfix (#3391)
* Fix popularManga and detailsManga * Cleanup * Fix searchManga and chapter pages * Rename and move mangaDeatilsDto * Fix lint * Fix available languages * Fix latestUpdate * Fix russian pages * Fix getMangaUrl * Update icons * Remove try/catch unneeded * Bump versionId * Add compatibility with previous version * Cleanup * Refactoring getMangaUrl
|
@ -1,7 +1,7 @@
|
||||||
ext {
|
ext {
|
||||||
extName = 'Union Mangas'
|
extName = 'Union Mangas'
|
||||||
extClass = '.UnionMangasFactory'
|
extClass = '.UnionMangasFactory'
|
||||||
extVersionCode = 3
|
extVersionCode = 4
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 9.0 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 14 KiB |
|
@ -1,6 +1,5 @@
|
||||||
package eu.kanade.tachiyomi.extension.all.unionmangas
|
package eu.kanade.tachiyomi.extension.all.unionmangas
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.lib.cryptoaes.CryptoAES
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||||
|
@ -10,22 +9,16 @@ import eu.kanade.tachiyomi.source.model.Page
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import org.jsoup.nodes.Document
|
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.security.MessageDigest
|
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.TimeZone
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
class UnionMangas(private val langOption: LanguageOption) : HttpSource() {
|
class UnionMangas(private val langOption: LanguageOption) : HttpSource() {
|
||||||
override val lang = langOption.lang
|
override val lang = langOption.lang
|
||||||
|
@ -38,39 +31,12 @@ class UnionMangas(private val langOption: LanguageOption) : HttpSource() {
|
||||||
|
|
||||||
private val json: Json by injectLazy()
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
val langApiInfix = when (lang) {
|
|
||||||
"it" -> langOption.infix
|
|
||||||
else -> "v3/po"
|
|
||||||
}
|
|
||||||
|
|
||||||
override val client = network.client.newBuilder()
|
override val client = network.client.newBuilder()
|
||||||
.rateLimit(5, 2, TimeUnit.SECONDS)
|
.rateLimit(2)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
private fun apiHeaders(url: String): Headers {
|
override fun headersBuilder(): Headers.Builder = super.headersBuilder()
|
||||||
val date = apiDateFormat.format(Date())
|
.set("Referer", "$baseUrl/")
|
||||||
val path = url.toUrlWithoutDomain()
|
|
||||||
|
|
||||||
return headersBuilder()
|
|
||||||
.add("_hash", authorization(apiSeed, domain, date))
|
|
||||||
.add("_tranId", authorization(apiSeed, domain, date, path))
|
|
||||||
.add("_date", date)
|
|
||||||
.add("_domain", domain)
|
|
||||||
.add("_path", path)
|
|
||||||
.add("Origin", baseUrl)
|
|
||||||
.add("Host", apiUrl.removeProtocol())
|
|
||||||
.add("Referer", "$baseUrl/")
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun authorization(vararg payloads: String): String {
|
|
||||||
val md = MessageDigest.getInstance("MD5")
|
|
||||||
val bytes = payloads.joinToString("").toByteArray()
|
|
||||||
val digest = md.digest(bytes)
|
|
||||||
return digest
|
|
||||||
.fold("") { str, byte -> str + "%02x".format(byte) }
|
|
||||||
.padStart(32, '0')
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun chapterListParse(response: Response) = throw UnsupportedOperationException()
|
override fun chapterListParse(response: Response) = throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
@ -79,95 +45,101 @@ class UnionMangas(private val langOption: LanguageOption) : HttpSource() {
|
||||||
var currentPage = 0
|
var currentPage = 0
|
||||||
do {
|
do {
|
||||||
val chaptersDto = fetchChapterListPageable(manga, currentPage)
|
val chaptersDto = fetchChapterListPageable(manga, currentPage)
|
||||||
chapters += chaptersDto.toSChapter(langOption)
|
chapters += chaptersDto.data.map { chapter ->
|
||||||
|
SChapter.create().apply {
|
||||||
|
name = chapter.name
|
||||||
|
date_upload = chapter.date.toDate()
|
||||||
|
url = chapter.toChapterUrl(langOption.infix)
|
||||||
|
}
|
||||||
|
}
|
||||||
currentPage++
|
currentPage++
|
||||||
} while (chaptersDto.hasNextPage())
|
} while (chaptersDto.hasNextPage())
|
||||||
return Observable.just(chapters.reversed())
|
return Observable.just(chapters)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchChapterListPageable(manga: SManga, page: Int): ChapterPageDto {
|
private fun fetchChapterListPageable(manga: SManga, page: Int): Pageable<ChapterDto> {
|
||||||
|
manga.apply {
|
||||||
|
url = getURLCompatibility(url)
|
||||||
|
}
|
||||||
|
|
||||||
val maxResult = 16
|
val maxResult = 16
|
||||||
val url = "$apiUrl/api/$langApiInfix/GetChapterListFilter/${manga.slug()}/$maxResult/$page/all/ASC"
|
val url = "$apiUrl/${langOption.infix}/GetChapterListFilter/${manga.slug()}/$maxResult/$page/all/ASC"
|
||||||
return client.newCall(GET(url, apiHeaders(url))).execute()
|
return client.newCall(GET(url, headers)).execute()
|
||||||
.parseAs<ChapterPageDto>()
|
.parseAs<Pageable<ChapterDto>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun latestUpdatesParse(response: Response): MangasPage {
|
override fun latestUpdatesParse(response: Response) = popularMangaParse(response)
|
||||||
val nextData = response.parseNextData<LatestUpdateProps>()
|
|
||||||
val dto = nextData.data.latestUpdateDto
|
|
||||||
val mangas = dto.mangas.map { mangaParse(it, nextData.query) }
|
|
||||||
|
|
||||||
|
override fun latestUpdatesRequest(page: Int): Request {
|
||||||
|
val maxResult = 24
|
||||||
|
val url = "$apiUrl/${langOption.infix}/HomeLastUpdate".toHttpUrl().newBuilder()
|
||||||
|
.addPathSegment("$maxResult")
|
||||||
|
.addPathSegment("${page - 1}")
|
||||||
|
.build()
|
||||||
|
return GET(url, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getMangaUrl(manga: SManga): String {
|
||||||
|
manga.apply {
|
||||||
|
url = getURLCompatibility(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseUrl + manga.url.replace(langOption.infix, langOption.mangaSubstring)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun mangaDetailsRequest(manga: SManga): Request {
|
||||||
|
manga.apply {
|
||||||
|
url = getURLCompatibility(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
val url = "$apiUrl/${langOption.infix}/getInfoManga".toHttpUrl().newBuilder()
|
||||||
|
.addPathSegment(manga.slug())
|
||||||
|
.build()
|
||||||
|
return GET(url, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun mangaDetailsParse(response: Response): SManga {
|
||||||
|
val dto = response.parseAs<MangaDetailsDto>()
|
||||||
|
return mangaParse(dto.details)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pageListRequest(chapter: SChapter): Request {
|
||||||
|
val chapterSlug = getURLCompatibility(chapter.url)
|
||||||
|
.substringAfter(langOption.infix)
|
||||||
|
|
||||||
|
val url = "$apiUrl/${langOption.infix}/GetImageChapter$chapterSlug"
|
||||||
|
return GET(url, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pageListParse(response: Response): List<Page> {
|
||||||
|
val location = response.request.url.toString()
|
||||||
|
val dto = response.parseAs<PageDto>()
|
||||||
|
return dto.pages.mapIndexed { index, url ->
|
||||||
|
Page(index, location, imageUrl = url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun popularMangaParse(response: Response): MangasPage {
|
||||||
|
val dto = response.parseAs<Pageable<MangaDto>>()
|
||||||
|
val mangas = dto.data.map(::mangaParse)
|
||||||
return MangasPage(
|
return MangasPage(
|
||||||
mangas = mangas,
|
mangas = mangas,
|
||||||
hasNextPage = dto.hasNextPage(),
|
hasNextPage = dto.hasNextPage(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun latestUpdatesRequest(page: Int): Request {
|
override fun popularMangaRequest(page: Int): Request {
|
||||||
val url = "$baseUrl/${langOption.infix}/latest-releases".toHttpUrl().newBuilder()
|
val maxResult = 24
|
||||||
.addQueryParameter("page", "$page")
|
return GET("$apiUrl/${langOption.infix}/HomeTopFllow/$maxResult/${page - 1}")
|
||||||
.build()
|
|
||||||
return GET(url, headers)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun mangaDetailsParse(response: Response): SManga {
|
|
||||||
val nextData = response.parseNextData<MangaDetailsProps>()
|
|
||||||
val dto = nextData.data.mangaDetailsDto
|
|
||||||
return SManga.create().apply {
|
|
||||||
title = dto.title
|
|
||||||
genre = dto.genres
|
|
||||||
thumbnail_url = dto.thumbnailUrl
|
|
||||||
url = mangaUrlParse(dto.slug, nextData.query.type)
|
|
||||||
status = dto.status
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun pageListParse(response: Response): List<Page> {
|
|
||||||
val chaptersDto = decryptChapters(response)
|
|
||||||
return chaptersDto.images.mapIndexed { index, imageUrl ->
|
|
||||||
Page(index, imageUrl = imageUrl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun decryptChapters(response: Response): ChaptersDto {
|
|
||||||
val document = response.asJsoup()
|
|
||||||
val password = findChapterPassword(document)
|
|
||||||
val pageListData = document.parseNextData<ChaptersProps>().data.pageListData
|
|
||||||
val decodedData = CryptoAES.decrypt(pageListData, password)
|
|
||||||
return ChaptersDto(
|
|
||||||
data = json.decodeFromString<ChaptersDto>(decodedData).data,
|
|
||||||
delimiter = langOption.pageDelimiter,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun findChapterPassword(document: Document): String {
|
|
||||||
val regxPasswordUrl = """\/pages\/%5Btype%5D\/%5Bidmanga%5D\/%5Biddetail%5D-.+\.js""".toRegex()
|
|
||||||
val regxFindPassword = """AES\.decrypt\(\w+,"(?<password>[^"]+)"\)""".toRegex(RegexOption.MULTILINE)
|
|
||||||
val jsDecryptUrl = document.select("script")
|
|
||||||
.map { it.absUrl("src") }
|
|
||||||
.first { regxPasswordUrl.find(it) != null }
|
|
||||||
val jsDecrypt = client.newCall(GET(jsDecryptUrl, headers)).execute().asJsoup().html()
|
|
||||||
return regxFindPassword.find(jsDecrypt)?.groups?.get("password")!!.value.trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun popularMangaParse(response: Response): MangasPage {
|
|
||||||
val dto = response.parseNextData<PopularMangaProps>()
|
|
||||||
val mangas = dto.data.mangas.map { it.details }.map { mangaParse(it, dto.query) }
|
|
||||||
return MangasPage(
|
|
||||||
mangas = mangas,
|
|
||||||
hasNextPage = false,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int) = GET("$baseUrl/${langOption.infix}")
|
|
||||||
|
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
val maxResult = 6
|
val maxResult = 20
|
||||||
val url = "$apiUrl/api/$langApiInfix/searchforms/$maxResult/".toHttpUrl().newBuilder()
|
val url = "$apiUrl/${langOption.infix}/QuickSearch/".toHttpUrl().newBuilder()
|
||||||
.addPathSegment(query)
|
.addPathSegment(query)
|
||||||
.addPathSegment("${page - 1}")
|
.addPathSegment("$maxResult")
|
||||||
.build()
|
.build()
|
||||||
return GET(url, apiHeaders(url.toString()))
|
return GET(url, headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
||||||
|
@ -185,52 +157,54 @@ class UnionMangas(private val langOption: LanguageOption) : HttpSource() {
|
||||||
override fun imageUrlParse(response: Response): String = ""
|
override fun imageUrlParse(response: Response): String = ""
|
||||||
|
|
||||||
override fun searchMangaParse(response: Response): MangasPage {
|
override fun searchMangaParse(response: Response): MangasPage {
|
||||||
val mangasDto = response.parseAs<MangaListDto>().apply {
|
val dto = response.parseAs<SearchDto>()
|
||||||
currentPage = response.request.url.pathSegments.last()
|
|
||||||
}
|
|
||||||
|
|
||||||
return MangasPage(
|
return MangasPage(
|
||||||
mangas = mangasDto.toSManga(langOption.infix),
|
dto.mangas.map(::mangaParse),
|
||||||
hasNextPage = mangasDto.hasNextPage(),
|
false,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private inline fun <reified T> Response.parseNextData() = asJsoup().parseNextData<T>()
|
/*
|
||||||
|
* Keeps compatibility with pt-BR previous version
|
||||||
|
* */
|
||||||
|
private fun getURLCompatibility(url: String): String {
|
||||||
|
val slugSuffix = "-br"
|
||||||
|
val mangaSubString = "manga-br"
|
||||||
|
|
||||||
private inline fun <reified T> Document.parseNextData(): NextData<T> {
|
val oldSlug = url.substringAfter(mangaSubString)
|
||||||
val jsonContent = selectFirst("script#__NEXT_DATA__")!!.html()
|
.substring(1)
|
||||||
return json.decodeFromString<NextData<T>>(jsonContent)
|
.split("/")
|
||||||
|
.first()
|
||||||
|
|
||||||
|
val newSlug = oldSlug.substringBeforeLast(slugSuffix)
|
||||||
|
|
||||||
|
return url.replace(oldSlug, newSlug)
|
||||||
}
|
}
|
||||||
|
|
||||||
private inline fun <reified T> Response.parseAs(): T {
|
private inline fun <reified T> Response.parseAs(): T {
|
||||||
return json.decodeFromString(body.string())
|
return json.decodeFromString(body.string())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun String.removeProtocol() = trim().replace("https://", "")
|
|
||||||
|
|
||||||
private fun SManga.slug() = this.url.split("/").last()
|
private fun SManga.slug() = this.url.split("/").last()
|
||||||
|
|
||||||
private fun String.toUrlWithoutDomain() = trim().replace(apiUrl, "")
|
private fun mangaParse(dto: MangaDto): SManga {
|
||||||
|
|
||||||
private fun mangaParse(dto: MangaDto, query: QueryDto): SManga {
|
|
||||||
return SManga.create().apply {
|
return SManga.create().apply {
|
||||||
title = dto.title
|
title = dto.title
|
||||||
thumbnail_url = dto.thumbnailUrl
|
thumbnail_url = dto.thumbnailUrl
|
||||||
status = dto.status
|
status = dto.status
|
||||||
url = mangaUrlParse(dto.slug, query.type)
|
url = "/${langOption.infix}/${dto.slug}"
|
||||||
genre = dto.genres
|
genre = dto.genres
|
||||||
|
initialized = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun mangaUrlParse(slug: String, pathSegment: String) = "/$pathSegment/$slug"
|
private fun String.toDate(): Long =
|
||||||
|
try { dateFormat.parse(trim())!!.time } catch (_: Exception) { 0L }
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val SEARCH_PREFIX = "slug:"
|
const val SEARCH_PREFIX = "slug:"
|
||||||
val apiUrl = "https://api.unionmanga.xyz"
|
val apiUrl = "https://app.unionmanga.xyz/api"
|
||||||
val apiSeed = "8e0550790c94d6abc71d738959a88d209690dc86"
|
val oldApiUrl = "https://api.unionmanga.xyz"
|
||||||
val domain = "yaoi-chan.xyz"
|
val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS", Locale.ENGLISH)
|
||||||
val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH)
|
|
||||||
val apiDateFormat = SimpleDateFormat("EE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.ENGLISH)
|
|
||||||
.apply { timeZone = TimeZone.getTimeZone("GMT") }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,149 +1,68 @@
|
||||||
package eu.kanade.tachiyomi.extension.all.unionmangas
|
package eu.kanade.tachiyomi.extension.all.unionmangas
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class NextData<T>(val props: Props<T>, val query: QueryDto) {
|
class MangaDetailsDto(private val data: Props) {
|
||||||
val data get() = props.pageProps
|
val details: MangaDto get() = data.details
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class Props(
|
||||||
|
@SerialName("infoDoc") val details: MangaDto,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class Props<T>(val pageProps: T)
|
open class Pageable<T>(
|
||||||
|
var currentPage: Int,
|
||||||
@Serializable
|
var totalPage: Int,
|
||||||
class PopularMangaProps(@SerialName("data_popular") val mangas: List<PopularMangaDto>)
|
val data: List<T>,
|
||||||
|
) {
|
||||||
@Serializable
|
fun hasNextPage() = (currentPage + 1) <= totalPage
|
||||||
class LatestUpdateProps(@SerialName("data_lastuppdate") val latestUpdateDto: MangaListDto)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
class MangaDetailsProps(@SerialName("dataManga") val mangaDetailsDto: MangaDetailsDto)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
class ChaptersProps(@SerialName("data") val pageListData: String)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
abstract class Pageable {
|
|
||||||
abstract var currentPage: String?
|
|
||||||
abstract var totalPage: Int
|
|
||||||
|
|
||||||
fun hasNextPage() =
|
|
||||||
try { (currentPage!!.toInt() + 1) < totalPage } catch (_: Exception) { false }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
class ChapterPageDto(
|
|
||||||
val totalRecode: Int = 0,
|
|
||||||
override var currentPage: String?,
|
|
||||||
override var totalPage: Int,
|
|
||||||
@SerialName("data") val chapters: List<ChapterDto> = emptyList(),
|
|
||||||
) : Pageable() {
|
|
||||||
fun toSChapter(langOption: LanguageOption): List<SChapter> =
|
|
||||||
chapters.map { chapter ->
|
|
||||||
SChapter.create().apply {
|
|
||||||
name = chapter.name
|
|
||||||
date_upload = chapter.date.toDate()
|
|
||||||
url = "/${langOption.infix}${chapter.toChapterUrl(langOption.chpPrefix)}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun String.toDate(): Long =
|
|
||||||
try { UnionMangas.dateFormat.parse(trim())!!.time } catch (_: Exception) { 0L }
|
|
||||||
|
|
||||||
private fun ChapterDto.toChapterUrl(prefix: String) = "/${this.slugManga}/$prefix-${this.id}"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class ChapterDto(
|
class ChapterDto(
|
||||||
val date: String,
|
val date: String,
|
||||||
val slug: String,
|
|
||||||
@SerialName("idDoc") val slugManga: String,
|
@SerialName("idDoc") val slugManga: String,
|
||||||
@SerialName("idDetail") val id: String,
|
@SerialName("idDetail") val id: String,
|
||||||
@SerialName("nameChapter") val name: String,
|
@SerialName("nameChapter") val name: String,
|
||||||
)
|
) {
|
||||||
|
fun toChapterUrl(lang: String) = "/$lang/${this.slugManga}/$id"
|
||||||
@Serializable
|
|
||||||
class QueryDto(
|
|
||||||
val type: String,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
class MangaListDto(
|
|
||||||
override var currentPage: String?,
|
|
||||||
override var totalPage: Int,
|
|
||||||
@SerialName("data") val mangas: List<MangaDto>,
|
|
||||||
) : Pageable() {
|
|
||||||
fun toSManga(siteLang: String) = mangas.map { dto ->
|
|
||||||
SManga.create().apply {
|
|
||||||
title = dto.title
|
|
||||||
thumbnail_url = dto.thumbnailUrl
|
|
||||||
status = dto.status
|
|
||||||
url = mangaUrlParse(dto.slug, siteLang)
|
|
||||||
genre = dto.genres
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
|
||||||
class PopularMangaDto(
|
|
||||||
@SerialName("document") val details: MangaDto,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class MangaDto(
|
class MangaDto(
|
||||||
@SerialName("name") val title: String,
|
@SerialName("name") val title: String,
|
||||||
@SerialName("image") private val _thumbnailUrl: String,
|
@SerialName("image") private val _thumbnailUrl: String,
|
||||||
@SerialName("idDoc") val slug: String,
|
@SerialName("idDoc") val slug: String,
|
||||||
@SerialName("genres") private val _genres: String,
|
@SerialName("genresName") val genres: String,
|
||||||
@SerialName("status") val _status: String,
|
@SerialName("status") val _status: String,
|
||||||
) {
|
) {
|
||||||
val thumbnailUrl get() = "${UnionMangas.apiUrl}$_thumbnailUrl"
|
val thumbnailUrl get() = "${UnionMangas.oldApiUrl}$_thumbnailUrl"
|
||||||
val genres get() = _genres.split(",").joinToString { it.trim() }
|
|
||||||
val status get() = toSMangaStatus(_status)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
val status get() = when (_status) {
|
||||||
class MangaDetailsDto(
|
|
||||||
@SerialName("name") val title: String,
|
|
||||||
@SerialName("image") private val _thumbnailUrl: String,
|
|
||||||
@SerialName("idDoc") val slug: String,
|
|
||||||
@SerialName("lsgenres") private val _genres: List<Prop>,
|
|
||||||
@SerialName("lsstatus") private val _status: List<Prop>,
|
|
||||||
) {
|
|
||||||
|
|
||||||
val thumbnailUrl get() = "${UnionMangas.apiUrl}$_thumbnailUrl"
|
|
||||||
val genres get() = _genres.joinToString { it.name }
|
|
||||||
val status get() = toSMangaStatus(_status.firstOrNull()?.name ?: "")
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
class Prop(
|
|
||||||
val name: String,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
class ChaptersDto(
|
|
||||||
@SerialName("dataManga") val data: PageDto,
|
|
||||||
private var delimiter: String = "",
|
|
||||||
) {
|
|
||||||
val images get() = data.getImages(delimiter)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
class PageDto(
|
|
||||||
@SerialName("source") private val imgData: String,
|
|
||||||
) {
|
|
||||||
fun getImages(delimiter: String): List<String> = imgData.split(delimiter)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun mangaUrlParse(slug: String, pathSegment: String) = "/$pathSegment/$slug"
|
|
||||||
|
|
||||||
private fun toSMangaStatus(status: String) =
|
|
||||||
when (status.lowercase()) {
|
|
||||||
"ongoing" -> SManga.ONGOING
|
"ongoing" -> SManga.ONGOING
|
||||||
"completed" -> SManga.COMPLETED
|
"completed" -> SManga.COMPLETED
|
||||||
else -> SManga.UNKNOWN
|
else -> SManga.UNKNOWN
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class SearchDto(
|
||||||
|
@SerialName("data")
|
||||||
|
val mangas: List<MangaDto>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class PageDto(val `data`: Data) {
|
||||||
|
val pages: List<String> get() = `data`.lsDetail.source.split("#")
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class Data(val lsDetail: LsDetail)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class LsDetail(val source: String)
|
||||||
|
}
|
||||||
|
|
|
@ -7,9 +7,9 @@ class UnionMangasFactory : SourceFactory {
|
||||||
override fun createSources(): List<Source> = languages.map { UnionMangas(it) }
|
override fun createSources(): List<Source> = languages.map { UnionMangas(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
class LanguageOption(val lang: String, val infix: String = lang, val chpPrefix: String, val pageDelimiter: String)
|
class LanguageOption(val lang: String, val infix: String = lang, val mangaSubstring: String = infix)
|
||||||
|
|
||||||
val languages = listOf(
|
val languages = listOf(
|
||||||
LanguageOption("it", "italy", "leer", ","),
|
LanguageOption("pt-BR", "manga-br"),
|
||||||
LanguageOption("pt-BR", "manga-br", "cap", "#"),
|
LanguageOption("ru", "manga-ru", "mangas"),
|
||||||
)
|
)
|
||||||
|
|