Refactor and fix some issues in HeanCms. (#14887)

Co-authored-by: Seew <90949336+seew3l@users.noreply.github.com>
This commit is contained in:
Alessandro Jean 2023-01-10 17:19:02 -03:00 committed by GitHub
parent a00ed03e63
commit 2bb71b034d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 68 additions and 62 deletions

View File

@ -14,7 +14,6 @@ import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.Headers import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
@ -109,6 +108,23 @@ abstract class HeanCms(
override fun latestUpdatesParse(response: Response): MangasPage = popularMangaParse(response) override fun latestUpdatesParse(response: Response): MangasPage = popularMangaParse(response)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
/**
* Their query search endpoint doesn't return the thumbnails, so we need to do
* later an special parsing to get the thumbnails as well from the slug map.
*/
if (query.isNotBlank()) {
val searchPayloadObj = HeanCmsSearchPayloadDto(query)
val searchPayload = json.encodeToString(searchPayloadObj)
.toRequestBody(JSON_MEDIA_TYPE)
val apiHeaders = headersBuilder()
.add("Accept", ACCEPT_JSON)
.add("Content-Type", searchPayload.contentType().toString())
.build()
return POST("$apiUrl/series/search", apiHeaders, searchPayload)
}
val sortByFilter = filters.firstInstanceOrNull<SortByFilter>() val sortByFilter = filters.firstInstanceOrNull<SortByFilter>()
val payloadObj = HeanCmsQuerySearchPayloadDto( val payloadObj = HeanCmsQuerySearchPayloadDto(
@ -123,42 +139,26 @@ abstract class HeanCms(
.orEmpty() .orEmpty()
) )
val searchPayloadObj = HeanCmsSearchPayloadDto(
term = query
)
val payload = json.encodeToString(payloadObj).toRequestBody(JSON_MEDIA_TYPE) val payload = json.encodeToString(payloadObj).toRequestBody(JSON_MEDIA_TYPE)
val searchPayload = json.encodeToString(searchPayloadObj).toRequestBody(JSON_MEDIA_TYPE)
val apiHeaders = headersBuilder() val apiHeaders = headersBuilder()
.add("Accept", ACCEPT_JSON) .add("Accept", ACCEPT_JSON)
.add("Content-Type", payload.contentType().toString()) .add("Content-Type", payload.contentType().toString())
.build() .build()
if (query.isNotBlank()) { return POST("$apiUrl/series/querysearch", apiHeaders, payload)
val apiUrl = "$apiUrl/series/search".toHttpUrl().newBuilder()
.toString()
return POST(apiUrl, apiHeaders, searchPayload)
}
val apiUrl = "$apiUrl/series/querysearch".toHttpUrl().newBuilder()
.toString()
return POST(apiUrl, apiHeaders, payload)
} }
override fun searchMangaParse(response: Response): MangasPage { override fun searchMangaParse(response: Response): MangasPage {
val json = response.body?.string().orEmpty() val json = response.body?.string().orEmpty()
if (response.request.url.pathSegments.last() == "search") { if (response.request.url.pathSegments.last() == "search") {
fetchAllTitles()
val result = json.parseAs<List<HeanCmsSearchDto>>() val result = json.parseAs<List<HeanCmsSearchDto>>()
val mangaList = result val mangaList = result
.filter { it.type == "Comic" } .filter { it.type == "Comic" }
.map { .map { it.toSManga(apiUrl, coverPath, seriesSlugMap.orEmpty()) }
createSearchResult(it.slug.replace(TIMESTAMP_REGEX, ""), it.title)
}
fetchAllTitles()
return MangasPage(mangaList, false) return MangasPage(mangaList, false)
} }
@ -180,23 +180,6 @@ abstract class HeanCms(
return MangasPage(mangaList, hasNextPage = false) return MangasPage(mangaList, hasNextPage = false)
} }
private fun createSearchResult(slug: String, title: String): SManga {
val theSeries = seriesSlugMap?.get(slug)
return SManga.create().apply {
url = "/series/$slug"
this.title = title
thumbnail_url = "$apiUrl/$coverPath${theSeries?.thumbnailUrl}"
this.status = when (theSeries?.status) {
"Ongoing" -> SManga.ONGOING
"Hiatus" -> SManga.ON_HIATUS
"Dropped" -> SManga.CANCELLED
"Completed", "Finished" -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
}
}
// Workaround to allow "Open in browser" use the real URL. // Workaround to allow "Open in browser" use the real URL.
override fun fetchMangaDetails(manga: SManga): Observable<SManga> { override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
return client.newCall(seriesDetailsRequest(manga)) return client.newCall(seriesDetailsRequest(manga))
@ -223,13 +206,15 @@ abstract class HeanCms(
fetchAllTitles() fetchAllTitles()
val currentSlug = seriesSlugMap?.get(seriesSlug)?.slug ?: seriesSlug val seriesDetails = seriesSlugMap?.get(seriesSlug)
val currentSlug = seriesDetails?.slug ?: seriesSlug
val currentStatus = seriesDetails?.status ?: SManga.UNKNOWN
val apiHeaders = headersBuilder() val apiHeaders = headersBuilder()
.add("Accept", ACCEPT_JSON) .add("Accept", ACCEPT_JSON)
.build() .build()
return GET("$apiUrl/series/$currentSlug#${manga.status}", apiHeaders) return GET("$apiUrl/series/$currentSlug#$currentStatus", apiHeaders)
} }
override fun mangaDetailsParse(response: Response): SManga { override fun mangaDetailsParse(response: Response): SManga {
@ -247,9 +232,11 @@ abstract class HeanCms(
override fun chapterListParse(response: Response): List<SChapter> { override fun chapterListParse(response: Response): List<SChapter> {
val result = response.parseAs<HeanCmsSeriesDto>() val result = response.parseAs<HeanCmsSeriesDto>()
val seriesSlug = response.request.url.pathSegments.last() val seriesSlug = response.request.url.pathSegments.last()
val currentTimestamp = System.currentTimeMillis()
return result.chapters.orEmpty() return result.chapters.orEmpty()
.map { it.toSChapter(seriesSlug) } .map { it.toSChapter(seriesSlug) }
.filter { it.date_upload <= currentTimestamp }
.reversed() .reversed()
} }
@ -265,7 +252,9 @@ abstract class HeanCms(
override fun pageListParse(response: Response): List<Page> { override fun pageListParse(response: Response): List<Page> {
return response.parseAs<HeanCmsReaderDto>().content?.images.orEmpty() return response.parseAs<HeanCmsReaderDto>().content?.images.orEmpty()
.mapIndexed { i, url -> Page(i, "", "$apiUrl/$url") } .mapIndexed { i, url ->
Page(i, imageUrl = if (url.startsWith("http")) url else "$apiUrl/$url")
}
} }
override fun fetchImageUrl(page: Page): Observable<String> = Observable.just(page.imageUrl!!) override fun fetchImageUrl(page: Page): Observable<String> = Observable.just(page.imageUrl!!)
@ -353,9 +342,9 @@ abstract class HeanCms(
keySelector = { it.slug.replace(TIMESTAMP_REGEX, "") }, keySelector = { it.slug.replace(TIMESTAMP_REGEX, "") },
valueTransform = { valueTransform = {
HeanCmsTitle( HeanCmsTitle(
it.slug, slug = it.slug,
it.thumbnail, thumbnailFileName = it.thumbnail,
it.status.orEmpty() status = it.status?.toStatus() ?: SManga.UNKNOWN
) )
} }
) )
@ -383,6 +372,12 @@ abstract class HeanCms(
private inline fun <reified R> List<*>.firstInstanceOrNull(): R? = private inline fun <reified R> List<*>.firstInstanceOrNull(): R? =
filterIsInstance<R>().firstOrNull() filterIsInstance<R>().firstOrNull()
/**
* Used to store the current slugs for sources that change it periodically and for the
* search that doesn't return the thumbnail URLs.
*/
data class HeanCmsTitle(val slug: String, val thumbnailFileName: String, val status: Int)
companion object { companion object {
private const val ACCEPT_IMAGE = "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8" private const val ACCEPT_IMAGE = "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8"
private const val ACCEPT_JSON = "application/json, text/plain, */*" private const val ACCEPT_JSON = "application/json, text/plain, */*"
@ -391,10 +386,4 @@ abstract class HeanCms(
val TIMESTAMP_REGEX = "-\\d+$".toRegex() val TIMESTAMP_REGEX = "-\\d+$".toRegex()
} }
data class HeanCmsTitle(
val slug: String,
val thumbnailUrl: String,
val status: String
)
} }

View File

@ -30,7 +30,24 @@ data class HeanCmsSearchDto(
@SerialName("series_slug") val slug: String, @SerialName("series_slug") val slug: String,
@SerialName("series_type") val type: String, @SerialName("series_type") val type: String,
val title: String val title: String
) ) {
fun toSManga(
apiUrl: String,
coverPath: String,
slugMap: Map<String, HeanCms.HeanCmsTitle>
): SManga = SManga.create().apply {
val slugOnly = slug.replace(HeanCms.TIMESTAMP_REGEX, "")
val thumbnailFileName = slugMap[slugOnly]?.thumbnailFileName.orEmpty()
title = this@HeanCmsSearchDto.title
thumbnail_url = when {
thumbnailFileName.isNotEmpty() -> "$apiUrl/$coverPath$thumbnailFileName"
else -> ""
}
url = "/series/$slugOnly"
}
}
@Serializable @Serializable
data class HeanCmsSeriesDto( data class HeanCmsSeriesDto(
@ -60,13 +77,7 @@ data class HeanCmsSeriesDto(
.sortedBy(HeanCmsTagDto::name) .sortedBy(HeanCmsTagDto::name)
.joinToString { it.name } .joinToString { it.name }
thumbnail_url = "$apiUrl/$coverPath$thumbnail" thumbnail_url = "$apiUrl/$coverPath$thumbnail"
status = when (this@HeanCmsSeriesDto.status) { status = this@HeanCmsSeriesDto.status?.toStatus() ?: SManga.UNKNOWN
"Ongoing" -> SManga.ONGOING
"Hiatus" -> SManga.ON_HIATUS
"Dropped" -> SManga.CANCELLED
"Completed", "Finished" -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
url = "/series/${slug.replace(HeanCms.TIMESTAMP_REGEX, "")}" url = "/series/${slug.replace(HeanCms.TIMESTAMP_REGEX, "")}"
} }
} }
@ -118,6 +129,12 @@ data class HeanCmsQuerySearchPayloadDto(
) )
@Serializable @Serializable
data class HeanCmsSearchPayloadDto( data class HeanCmsSearchPayloadDto(val term: String)
val term: String,
) fun String.toStatus(): Int = when (this) {
"Ongoing" -> SManga.ONGOING
"Hiatus" -> SManga.ON_HIATUS
"Dropped" -> SManga.CANCELLED
"Completed", "Finished" -> SManga.COMPLETED
else -> SManga.UNKNOWN
}

View File

@ -9,7 +9,7 @@ class HeanCmsGenerator : ThemeSourceGenerator {
override val themeClass = "HeanCms" override val themeClass = "HeanCms"
override val baseVersionCode: Int = 7 override val baseVersionCode: Int = 8
override val sources = listOf( override val sources = listOf(
SingleLang("Reaper Scans", "https://reaperscans.net", "pt-BR", overrideVersionCode = 35), SingleLang("Reaper Scans", "https://reaperscans.net", "pt-BR", overrideVersionCode = 35),