mirror of
https://github.com/keiyoushi/extensions-source.git
synced 2024-11-22 02:12:42 +01:00
Update HeanCMS theme (#1969)
* i hate this theme * bump * remove useless slug update * lint * Update series slug on chapter list update This was made for sources that changed slugs constantly. Currently no one uses it, but who knows if they enable that again * what an unstable experience * Remove empty lines * Fix intl * newline Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com> * why my build took 5 minutes * I hate iguanas --------- Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
This commit is contained in:
parent
16011407c5
commit
8defa56f71
17
lib-multisrc/heancms/assets/i18n/messages_en.properties
Normal file
17
lib-multisrc/heancms/assets/i18n/messages_en.properties
Normal file
@ -0,0 +1,17 @@
|
||||
genre_filter_title=Genres
|
||||
status_filter_title=Status
|
||||
status_all=All
|
||||
status_ongoing=Ongoing
|
||||
status_onhiatus=On hiatus
|
||||
status_dropped=Dropped
|
||||
sort_by_filter_title=Sort By
|
||||
sort_by_title=Title
|
||||
sort_by_views=Views
|
||||
sort_by_latest=Latest
|
||||
sort_by_created_at=Created at
|
||||
pref_show_paid_chapter_title=Display paid chapters
|
||||
pref_show_paid_chapter_summary_on=Paid chapters will appear.
|
||||
pref_show_paid_chapter_summary_off=Only free chapters will be displayed.
|
||||
url_changed_error=The URL of the series has changed. Migrate from %s to %s to update the URL
|
||||
paid_chapter_error=Paid chapter unavailable.
|
||||
id_not_found_error=Failed to get the ID for slug: %s
|
17
lib-multisrc/heancms/assets/i18n/messages_es.properties
Normal file
17
lib-multisrc/heancms/assets/i18n/messages_es.properties
Normal file
@ -0,0 +1,17 @@
|
||||
genre_filter_title=Géneros
|
||||
status_filter_title=Estado
|
||||
status_all=Todos
|
||||
status_ongoing=En curso
|
||||
status_onhiatus=En hiatus
|
||||
status_dropped=Abandonada
|
||||
sort_by_filter_title=Ordenar por
|
||||
sort_by_title=Título
|
||||
sort_by_views=Número de vistas
|
||||
sort_by_latest=Recientes
|
||||
sort_by_created_at=Fecha de creación
|
||||
pref_show_paid_chapter_title=Mostrar capítulos de pago
|
||||
pref_show_paid_chapter_summary_on=Se mostrarán capítulos de pago.
|
||||
pref_show_paid_chapter_summary_off=Solo se mostrarán los capítulos gratuitos.
|
||||
url_changed_error= La URL de la serie ha cambiado. Migre de %s a %s para actualizar la URL
|
||||
paid_chapter_error=Capítulo no disponible.
|
||||
id_not_found_error=No se pudo encontrar el ID para: %s
|
13
lib-multisrc/heancms/assets/i18n/messages_pt_br.properties
Normal file
13
lib-multisrc/heancms/assets/i18n/messages_pt_br.properties
Normal file
@ -0,0 +1,13 @@
|
||||
genre_filter_title=Gêneros
|
||||
status_filter_title=Estado
|
||||
status_all=Todos
|
||||
status_ongoing=Em andamento
|
||||
status_onhiatus=Em hiato
|
||||
status_dropped=Cancelada
|
||||
sort_by_filter_title=Ordenar por
|
||||
sort_by_title=Título
|
||||
sort_by_views=Visualizações
|
||||
sort_by_latest=Recentes
|
||||
sort_by_created_at=Data de criação
|
||||
url_changed_error=A URL da série mudou. Migre de %s para %s para atualizar a URL
|
||||
id_not_found_error=Falha ao obter o ID do slug: %s
|
@ -2,4 +2,8 @@ plugins {
|
||||
id("lib-multisrc")
|
||||
}
|
||||
|
||||
baseVersionCode = 20
|
||||
baseVersionCode = 21
|
||||
|
||||
dependencies {
|
||||
api(project(":lib:i18n"))
|
||||
}
|
||||
|
@ -4,31 +4,25 @@ import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import androidx.preference.PreferenceScreen
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import eu.kanade.tachiyomi.lib.i18n.Intl
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Headers
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okhttp3.Response
|
||||
import rx.Observable
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.io.IOException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
@ -39,35 +33,15 @@ abstract class HeanCms(
|
||||
protected val apiUrl: String = baseUrl.replace("://", "://api."),
|
||||
) : ConfigurableSource, HttpSource() {
|
||||
|
||||
private val preferences: SharedPreferences by lazy {
|
||||
protected val preferences: SharedPreferences by lazy {
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
}
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
SwitchPreferenceCompat(screen.context).apply {
|
||||
key = SHOW_PAID_CHAPTERS_PREF
|
||||
title = intl.prefShowPaidChapterTitle
|
||||
summaryOn = intl.prefShowPaidChapterSummaryOn
|
||||
summaryOff = intl.prefShowPaidChapterSummaryOff
|
||||
setDefaultValue(SHOW_PAID_CHAPTERS_DEFAULT)
|
||||
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
preferences.edit()
|
||||
.putBoolean(SHOW_PAID_CHAPTERS_PREF, newValue as Boolean)
|
||||
.commit()
|
||||
}
|
||||
}.also(screen::addPreference)
|
||||
}
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
override val client: OkHttpClient = network.cloudflareClient
|
||||
|
||||
protected open val slugStrategy = SlugStrategy.NONE
|
||||
|
||||
protected open val useNewQueryEndpoint = false
|
||||
|
||||
private var seriesSlugMap: Map<String, HeanCmsTitle>? = null
|
||||
protected open val useNewChapterEndpoint = false
|
||||
|
||||
/**
|
||||
* Custom Json instance to make usage of `encodeDefaults`,
|
||||
@ -79,9 +53,14 @@ abstract class HeanCms(
|
||||
encodeDefaults = true
|
||||
}
|
||||
|
||||
protected val intl by lazy { HeanCmsIntl(lang) }
|
||||
protected val intl = Intl(
|
||||
language = lang,
|
||||
baseLanguage = "en",
|
||||
availableLanguages = setOf("en", "pt-BR", "es"),
|
||||
classLoader = this::class.java.classLoader!!,
|
||||
)
|
||||
|
||||
protected open val coverPath: String = "cover/"
|
||||
protected open val coverPath: String = ""
|
||||
|
||||
protected open val mangaSubDirectory: String = "series"
|
||||
|
||||
@ -92,29 +71,6 @@ abstract class HeanCms(
|
||||
.add("Referer", "$baseUrl/")
|
||||
|
||||
override fun popularMangaRequest(page: Int): Request {
|
||||
if (useNewQueryEndpoint) {
|
||||
return newEndpointPopularMangaRequest(page)
|
||||
}
|
||||
|
||||
val payloadObj = HeanCmsQuerySearchPayloadDto(
|
||||
page = page,
|
||||
order = "desc",
|
||||
orderBy = "total_views",
|
||||
status = "All",
|
||||
type = "Comic",
|
||||
)
|
||||
|
||||
val payload = json.encodeToString(payloadObj).toRequestBody(JSON_MEDIA_TYPE)
|
||||
|
||||
val apiHeaders = headersBuilder()
|
||||
.add("Accept", ACCEPT_JSON)
|
||||
.add("Content-Type", payload.contentType().toString())
|
||||
.build()
|
||||
|
||||
return POST("$apiUrl/series/querysearch", apiHeaders, payload)
|
||||
}
|
||||
|
||||
protected fun newEndpointPopularMangaRequest(page: Int): Request {
|
||||
val url = "$apiUrl/query".toHttpUrl().newBuilder()
|
||||
.addQueryParameter("query_string", "")
|
||||
.addQueryParameter("series_status", "All")
|
||||
@ -124,66 +80,14 @@ abstract class HeanCms(
|
||||
.addQueryParameter("page", page.toString())
|
||||
.addQueryParameter("perPage", "12")
|
||||
.addQueryParameter("tags_ids", "[]")
|
||||
.addQueryParameter("adult", "true")
|
||||
|
||||
return GET(url.build(), headers)
|
||||
}
|
||||
|
||||
override fun popularMangaParse(response: Response): MangasPage {
|
||||
val json = response.body.string()
|
||||
|
||||
if (json.startsWith("{")) {
|
||||
val result = json.parseAs<HeanCmsQuerySearchDto>()
|
||||
val mangaList = result.data.map {
|
||||
if (slugStrategy != SlugStrategy.NONE) {
|
||||
preferences.slugMap = preferences.slugMap.toMutableMap()
|
||||
.also { map -> map[it.slug.toPermSlugIfNeeded()] = it.slug }
|
||||
}
|
||||
it.toSManga(apiUrl, coverPath, mangaSubDirectory, slugStrategy)
|
||||
}
|
||||
|
||||
fetchAllTitles()
|
||||
|
||||
return MangasPage(mangaList, result.meta?.hasNextPage ?: false)
|
||||
}
|
||||
|
||||
val mangaList = json.parseAs<List<HeanCmsSeriesDto>>()
|
||||
.map {
|
||||
if (slugStrategy != SlugStrategy.NONE) {
|
||||
preferences.slugMap = preferences.slugMap.toMutableMap()
|
||||
.also { map -> map[it.slug.toPermSlugIfNeeded()] = it.slug }
|
||||
}
|
||||
it.toSManga(apiUrl, coverPath, mangaSubDirectory, slugStrategy)
|
||||
}
|
||||
|
||||
fetchAllTitles()
|
||||
|
||||
return MangasPage(mangaList, hasNextPage = false)
|
||||
}
|
||||
override fun popularMangaParse(response: Response) = searchMangaParse(response)
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request {
|
||||
if (useNewQueryEndpoint) {
|
||||
return newEndpointLatestUpdatesRequest(page)
|
||||
}
|
||||
|
||||
val payloadObj = HeanCmsQuerySearchPayloadDto(
|
||||
page = page,
|
||||
order = "desc",
|
||||
orderBy = "latest",
|
||||
status = "All",
|
||||
type = "Comic",
|
||||
)
|
||||
|
||||
val payload = json.encodeToString(payloadObj).toRequestBody(JSON_MEDIA_TYPE)
|
||||
|
||||
val apiHeaders = headersBuilder()
|
||||
.add("Accept", ACCEPT_JSON)
|
||||
.add("Content-Type", payload.contentType().toString())
|
||||
.build()
|
||||
|
||||
return POST("$apiUrl/series/querysearch", apiHeaders, payload)
|
||||
}
|
||||
|
||||
protected fun newEndpointLatestUpdatesRequest(page: Int): Request {
|
||||
val url = "$apiUrl/query".toHttpUrl().newBuilder()
|
||||
.addQueryParameter("query_string", "")
|
||||
.addQueryParameter("series_status", "All")
|
||||
@ -193,6 +97,7 @@ abstract class HeanCms(
|
||||
.addQueryParameter("page", page.toString())
|
||||
.addQueryParameter("perPage", "12")
|
||||
.addQueryParameter("tags_ids", "[]")
|
||||
.addQueryParameter("adult", "true")
|
||||
|
||||
return GET(url.build(), headers)
|
||||
}
|
||||
@ -206,12 +111,8 @@ abstract class HeanCms(
|
||||
|
||||
val slug = query.substringAfter(SEARCH_PREFIX)
|
||||
val manga = SManga.create().apply {
|
||||
url = if (slugStrategy != SlugStrategy.NONE) {
|
||||
val mangaId = getIdBySlug(slug)
|
||||
"/$mangaSubDirectory/${slug.toPermSlugIfNeeded()}#$mangaId"
|
||||
} else {
|
||||
"/$mangaSubDirectory/$slug"
|
||||
}
|
||||
val mangaId = getIdBySlug(slug)
|
||||
url = "/$mangaSubDirectory/$slug#$mangaId"
|
||||
}
|
||||
|
||||
return fetchMangaDetails(manga).map { MangasPage(listOf(it), false) }
|
||||
@ -224,57 +125,12 @@ abstract class HeanCms(
|
||||
|
||||
val seriesDetail = json.parseAs<HeanCmsSeriesDto>()
|
||||
|
||||
preferences.slugMap = preferences.slugMap.toMutableMap()
|
||||
.also { it[seriesDetail.slug.toPermSlugIfNeeded()] = seriesDetail.slug }
|
||||
|
||||
seriesDetail.id
|
||||
}
|
||||
return result.getOrNull() ?: throw Exception(intl.idNotFoundError + slug)
|
||||
return result.getOrNull() ?: throw Exception(intl.format("id_not_found_error", slug))
|
||||
}
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
if (useNewQueryEndpoint) {
|
||||
return newEndpointSearchMangaRequest(page, query, filters)
|
||||
}
|
||||
|
||||
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 payloadObj = HeanCmsQuerySearchPayloadDto(
|
||||
page = page,
|
||||
order = if (sortByFilter?.state?.ascending == true) "asc" else "desc",
|
||||
orderBy = sortByFilter?.selected ?: "total_views",
|
||||
status = filters.firstInstanceOrNull<StatusFilter>()?.selected?.value ?: "Ongoing",
|
||||
type = "Comic",
|
||||
tagIds = filters.firstInstanceOrNull<GenreFilter>()?.state
|
||||
?.filter(Genre::state)
|
||||
?.map(Genre::id)
|
||||
.orEmpty(),
|
||||
)
|
||||
|
||||
val payload = json.encodeToString(payloadObj).toRequestBody(JSON_MEDIA_TYPE)
|
||||
|
||||
val apiHeaders = headersBuilder()
|
||||
.add("Accept", ACCEPT_JSON)
|
||||
.add("Content-Type", payload.contentType().toString())
|
||||
.build()
|
||||
|
||||
return POST("$apiUrl/series/querysearch", apiHeaders, payload)
|
||||
}
|
||||
|
||||
protected fun newEndpointSearchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val sortByFilter = filters.firstInstanceOrNull<SortByFilter>()
|
||||
val statusFilter = filters.firstInstanceOrNull<StatusFilter>()
|
||||
|
||||
@ -292,6 +148,7 @@ abstract class HeanCms(
|
||||
.addQueryParameter("page", page.toString())
|
||||
.addQueryParameter("perPage", "12")
|
||||
.addQueryParameter("tags_ids", tagIds)
|
||||
.addQueryParameter("adult", "true")
|
||||
|
||||
return GET(url.build(), headers)
|
||||
}
|
||||
@ -299,95 +156,34 @@ abstract class HeanCms(
|
||||
override fun searchMangaParse(response: Response): MangasPage {
|
||||
val json = response.body.string()
|
||||
|
||||
if (response.request.url.pathSegments.last() == "search") {
|
||||
fetchAllTitles()
|
||||
|
||||
val result = json.parseAs<List<HeanCmsSearchDto>>()
|
||||
val mangaList = result
|
||||
.filter { it.type == "Comic" }
|
||||
.map {
|
||||
it.slug = it.slug.toPermSlugIfNeeded()
|
||||
it.toSManga(apiUrl, coverPath, mangaSubDirectory, seriesSlugMap.orEmpty(), slugStrategy)
|
||||
}
|
||||
|
||||
return MangasPage(mangaList, false)
|
||||
val result = json.parseAs<HeanCmsQuerySearchDto>()
|
||||
val mangaList = result.data.map {
|
||||
it.toSManga(apiUrl, coverPath, mangaSubDirectory)
|
||||
}
|
||||
|
||||
if (json.startsWith("{")) {
|
||||
val result = json.parseAs<HeanCmsQuerySearchDto>()
|
||||
val mangaList = result.data.map {
|
||||
if (slugStrategy != SlugStrategy.NONE) {
|
||||
preferences.slugMap = preferences.slugMap.toMutableMap()
|
||||
.also { map -> map[it.slug.toPermSlugIfNeeded()] = it.slug }
|
||||
}
|
||||
it.toSManga(apiUrl, coverPath, mangaSubDirectory, slugStrategy)
|
||||
}
|
||||
|
||||
fetchAllTitles()
|
||||
|
||||
return MangasPage(mangaList, result.meta?.hasNextPage ?: false)
|
||||
}
|
||||
|
||||
val mangaList = json.parseAs<List<HeanCmsSeriesDto>>()
|
||||
.map {
|
||||
if (slugStrategy != SlugStrategy.NONE) {
|
||||
preferences.slugMap = preferences.slugMap.toMutableMap()
|
||||
.also { map -> map[it.slug.toPermSlugIfNeeded()] = it.slug }
|
||||
}
|
||||
it.toSManga(apiUrl, coverPath, mangaSubDirectory, slugStrategy)
|
||||
}
|
||||
|
||||
fetchAllTitles()
|
||||
|
||||
return MangasPage(mangaList, hasNextPage = false)
|
||||
return MangasPage(mangaList, result.meta?.hasNextPage() ?: false)
|
||||
}
|
||||
|
||||
override fun getMangaUrl(manga: SManga): String {
|
||||
val seriesSlug = manga.url
|
||||
.substringAfterLast("/")
|
||||
.substringBefore("#")
|
||||
.toPermSlugIfNeeded()
|
||||
|
||||
val currentSlug = if (slugStrategy != SlugStrategy.NONE) {
|
||||
preferences.slugMap[seriesSlug] ?: seriesSlug
|
||||
} else {
|
||||
seriesSlug
|
||||
}
|
||||
|
||||
return "$baseUrl/$mangaSubDirectory/$currentSlug"
|
||||
return "$baseUrl/$mangaSubDirectory/$seriesSlug"
|
||||
}
|
||||
|
||||
override fun mangaDetailsRequest(manga: SManga): Request {
|
||||
if (slugStrategy != SlugStrategy.NONE && (manga.url.contains(TIMESTAMP_REGEX))) {
|
||||
throw Exception(intl.urlChangedError(name))
|
||||
if (!manga.url.contains("#")) {
|
||||
throw Exception(intl.format("url_changed_error", name, name))
|
||||
}
|
||||
|
||||
if (slugStrategy == SlugStrategy.ID && !manga.url.contains("#")) {
|
||||
throw Exception(intl.urlChangedError(name))
|
||||
}
|
||||
|
||||
val seriesSlug = manga.url
|
||||
.substringAfterLast("/")
|
||||
.substringBefore("#")
|
||||
.toPermSlugIfNeeded()
|
||||
|
||||
val seriesId = manga.url.substringAfterLast("#")
|
||||
|
||||
fetchAllTitles()
|
||||
|
||||
val seriesDetails = seriesSlugMap?.get(seriesSlug)
|
||||
val currentSlug = seriesDetails?.slug ?: seriesSlug
|
||||
val currentStatus = seriesDetails?.status ?: manga.status
|
||||
|
||||
val apiHeaders = headersBuilder()
|
||||
.add("Accept", ACCEPT_JSON)
|
||||
.build()
|
||||
|
||||
return if (slugStrategy == SlugStrategy.ID) {
|
||||
GET("$apiUrl/series/id/$seriesId", apiHeaders)
|
||||
} else {
|
||||
GET("$apiUrl/series/$currentSlug#$currentStatus", apiHeaders)
|
||||
}
|
||||
return GET("$apiUrl/series/id/$seriesId", apiHeaders)
|
||||
}
|
||||
|
||||
override fun mangaDetailsParse(response: Response): SManga {
|
||||
@ -395,14 +191,10 @@ abstract class HeanCms(
|
||||
|
||||
val result = runCatching { response.parseAs<HeanCmsSeriesDto>() }
|
||||
|
||||
val seriesResult = result.getOrNull() ?: throw Exception(intl.urlChangedError(name))
|
||||
val seriesResult = result.getOrNull()
|
||||
?: throw Exception(intl.format("url_changed_error", name, name))
|
||||
|
||||
if (slugStrategy != SlugStrategy.NONE) {
|
||||
preferences.slugMap = preferences.slugMap.toMutableMap()
|
||||
.also { it[seriesResult.slug.toPermSlugIfNeeded()] = seriesResult.slug }
|
||||
}
|
||||
|
||||
val seriesDetails = seriesResult.toSManga(apiUrl, coverPath, mangaSubDirectory, slugStrategy)
|
||||
val seriesDetails = seriesResult.toSManga(apiUrl, coverPath, mangaSubDirectory)
|
||||
|
||||
return seriesDetails.apply {
|
||||
status = status.takeUnless { it == SManga.UNKNOWN }
|
||||
@ -410,105 +202,97 @@ abstract class HeanCms(
|
||||
}
|
||||
}
|
||||
|
||||
override fun chapterListRequest(manga: SManga): Request = mangaDetailsRequest(manga)
|
||||
override fun chapterListRequest(manga: SManga): Request {
|
||||
if (useNewChapterEndpoint) {
|
||||
if (!manga.url.contains("#")) {
|
||||
throw Exception(intl.format("url_changed_error", name, name))
|
||||
}
|
||||
|
||||
override fun chapterListParse(response: Response): List<SChapter> {
|
||||
val result = response.parseAs<HeanCmsSeriesDto>()
|
||||
val seriesId = manga.url.substringAfterLast("#")
|
||||
val seriesSlug = manga.url.substringAfterLast("/").substringBefore("#")
|
||||
|
||||
if (slugStrategy == SlugStrategy.ID) {
|
||||
preferences.slugMap = preferences.slugMap.toMutableMap()
|
||||
.also { it[result.slug.toPermSlugIfNeeded()] = result.slug }
|
||||
val url = "$apiUrl/chapter/query".toHttpUrl().newBuilder()
|
||||
.addQueryParameter("page", "1")
|
||||
.addQueryParameter("perPage", PER_PAGE_CHAPTERS.toString())
|
||||
.addQueryParameter("series_id", seriesId)
|
||||
.fragment(seriesSlug)
|
||||
|
||||
return GET(url.build(), headers)
|
||||
}
|
||||
|
||||
val currentTimestamp = System.currentTimeMillis()
|
||||
return mangaDetailsRequest(manga)
|
||||
}
|
||||
|
||||
override fun chapterListParse(response: Response): List<SChapter> {
|
||||
val showPaidChapters = preferences.showPaidChapters
|
||||
|
||||
if (useNewQueryEndpoint) {
|
||||
return result.seasons.orEmpty()
|
||||
.flatMap { it.chapters.orEmpty() }
|
||||
if (useNewChapterEndpoint) {
|
||||
val apiHeaders = headersBuilder()
|
||||
.add("Accept", ACCEPT_JSON)
|
||||
.build()
|
||||
|
||||
val seriesId = response.request.url.queryParameter("series_id")
|
||||
|
||||
val seriesSlug = response.request.url.fragment!!
|
||||
|
||||
var result = response.parseAs<HeanCmsChapterPayloadDto>()
|
||||
|
||||
val currentTimestamp = System.currentTimeMillis()
|
||||
|
||||
val chapterList = mutableListOf<HeanCmsChapterDto>()
|
||||
|
||||
chapterList.addAll(result.data)
|
||||
|
||||
var page = 2
|
||||
while (result.meta.hasNextPage()) {
|
||||
val url = "$apiUrl/chapter/query".toHttpUrl().newBuilder()
|
||||
.addQueryParameter("page", page.toString())
|
||||
.addQueryParameter("perPage", PER_PAGE_CHAPTERS.toString())
|
||||
.addQueryParameter("series_id", seriesId)
|
||||
.build()
|
||||
|
||||
val nextResponse = client.newCall(GET(url, apiHeaders)).execute()
|
||||
result = nextResponse.parseAs<HeanCmsChapterPayloadDto>()
|
||||
chapterList.addAll(result.data)
|
||||
page++
|
||||
}
|
||||
|
||||
return chapterList
|
||||
.filter { it.price == 0 || showPaidChapters }
|
||||
.map { it.toSChapter(result.slug, mangaSubDirectory, dateFormat, slugStrategy) }
|
||||
.map { it.toSChapter(seriesSlug, mangaSubDirectory, dateFormat) }
|
||||
.filter { it.date_upload <= currentTimestamp }
|
||||
}
|
||||
|
||||
return result.chapters.orEmpty()
|
||||
val result = response.parseAs<HeanCmsSeriesDto>()
|
||||
|
||||
val currentTimestamp = System.currentTimeMillis()
|
||||
|
||||
return result.seasons.orEmpty()
|
||||
.flatMap { it.chapters.orEmpty() }
|
||||
.filter { it.price == 0 || showPaidChapters }
|
||||
.map { it.toSChapter(result.slug, mangaSubDirectory, dateFormat, slugStrategy) }
|
||||
.map { it.toSChapter(result.slug, mangaSubDirectory, dateFormat) }
|
||||
.filter { it.date_upload <= currentTimestamp }
|
||||
.reversed()
|
||||
}
|
||||
|
||||
override fun getChapterUrl(chapter: SChapter): String {
|
||||
if (slugStrategy == SlugStrategy.NONE) return baseUrl + chapter.url
|
||||
override fun getChapterUrl(chapter: SChapter) = baseUrl + chapter.url.substringBeforeLast("#")
|
||||
|
||||
val seriesSlug = chapter.url
|
||||
.substringAfter("/$mangaSubDirectory/")
|
||||
.substringBefore("/")
|
||||
.toPermSlugIfNeeded()
|
||||
|
||||
val currentSlug = preferences.slugMap[seriesSlug] ?: seriesSlug
|
||||
val chapterUrl = chapter.url.replaceFirst(seriesSlug, currentSlug)
|
||||
|
||||
return baseUrl + chapterUrl
|
||||
}
|
||||
|
||||
override fun pageListRequest(chapter: SChapter): Request {
|
||||
if (useNewQueryEndpoint) {
|
||||
if (slugStrategy != SlugStrategy.NONE) {
|
||||
val seriesPermSlug = chapter.url.substringAfter("/$mangaSubDirectory/").substringBefore("/")
|
||||
val seriesSlug = preferences.slugMap[seriesPermSlug] ?: seriesPermSlug
|
||||
val chapterUrl = chapter.url.replaceFirst(seriesPermSlug, seriesSlug)
|
||||
return GET(baseUrl + chapterUrl, headers)
|
||||
}
|
||||
return GET(baseUrl + chapter.url, headers)
|
||||
}
|
||||
|
||||
val chapterId = chapter.url.substringAfterLast("#").substringBefore("-paid")
|
||||
|
||||
val apiHeaders = headersBuilder()
|
||||
.add("Accept", ACCEPT_JSON)
|
||||
.build()
|
||||
|
||||
return GET("$apiUrl/series/chapter/$chapterId", apiHeaders)
|
||||
}
|
||||
override fun pageListRequest(chapter: SChapter) =
|
||||
GET(apiUrl + chapter.url.replace("/$mangaSubDirectory/", "/chapter/"), headers)
|
||||
|
||||
override fun pageListParse(response: Response): List<Page> {
|
||||
if (useNewQueryEndpoint) {
|
||||
val paidChapter = response.request.url.fragment?.contains("-paid")
|
||||
val result = response.parseAs<HeanCmsPagePayloadDto>()
|
||||
|
||||
val document = response.asJsoup()
|
||||
if (result.isPaywalled()) throw Exception(intl["paid_chapter_error"])
|
||||
|
||||
val images = document.selectFirst("div.min-h-screen > div.container > p.items-center")
|
||||
|
||||
if (images == null && paidChapter == true) {
|
||||
throw IOException(intl.paidChapterError)
|
||||
return if (useNewChapterEndpoint) {
|
||||
result.chapter.chapterData?.images.orEmpty().mapIndexed { i, img ->
|
||||
Page(i, imageUrl = img)
|
||||
}
|
||||
|
||||
return images?.select("img").orEmpty().mapIndexed { i, img ->
|
||||
val imageUrl = if (img.hasClass("lazy")) img.absUrl("data-src") else img.absUrl("src")
|
||||
Page(i, "", imageUrl)
|
||||
} else {
|
||||
result.data.orEmpty().mapIndexed { i, img ->
|
||||
Page(i, imageUrl = img)
|
||||
}
|
||||
}
|
||||
|
||||
val images = response.parseAs<HeanCmsReaderDto>().content?.images.orEmpty()
|
||||
val paidChapter = response.request.url.fragment?.contains("-paid")
|
||||
|
||||
if (images.isEmpty() && paidChapter == true) {
|
||||
throw IOException(intl.paidChapterError)
|
||||
}
|
||||
|
||||
return images.filterNot { imageUrl ->
|
||||
// Their image server returns HTTP 403 for hidden files that starts
|
||||
// with a dot in the file name. To avoid download errors, these are removed.
|
||||
imageUrl
|
||||
.removeSuffix("/")
|
||||
.substringAfterLast("/")
|
||||
.startsWith(".")
|
||||
}
|
||||
.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!!)
|
||||
@ -523,121 +307,18 @@ abstract class HeanCms(
|
||||
return GET(page.imageUrl!!, imageHeaders)
|
||||
}
|
||||
|
||||
protected open fun fetchAllTitles() {
|
||||
if (!seriesSlugMap.isNullOrEmpty() || slugStrategy != SlugStrategy.FETCH_ALL) {
|
||||
return
|
||||
}
|
||||
|
||||
val result = runCatching {
|
||||
var hasNextPage = true
|
||||
var page = 1
|
||||
val tempMap = mutableMapOf<String, HeanCmsTitle>()
|
||||
|
||||
while (hasNextPage) {
|
||||
val response = client.newCall(allTitlesRequest(page)).execute()
|
||||
val json = response.body.string()
|
||||
|
||||
if (json.startsWith("{")) {
|
||||
val result = json.parseAs<HeanCmsQuerySearchDto>()
|
||||
tempMap.putAll(parseAllTitles(result.data))
|
||||
hasNextPage = result.meta?.hasNextPage ?: false
|
||||
page++
|
||||
} else {
|
||||
val result = json.parseAs<List<HeanCmsSeriesDto>>()
|
||||
tempMap.putAll(parseAllTitles(result))
|
||||
hasNextPage = false
|
||||
}
|
||||
}
|
||||
|
||||
tempMap.toMap()
|
||||
}
|
||||
|
||||
seriesSlugMap = result.getOrNull()
|
||||
preferences.slugMap = preferences.slugMap.toMutableMap()
|
||||
.also { it.putAll(seriesSlugMap.orEmpty().mapValues { (_, v) -> v.slug }) }
|
||||
}
|
||||
|
||||
protected open fun allTitlesRequest(page: Int): Request {
|
||||
if (useNewQueryEndpoint) {
|
||||
val url = "$apiUrl/query".toHttpUrl().newBuilder()
|
||||
.addQueryParameter("series_type", "Comic")
|
||||
.addQueryParameter("page", page.toString())
|
||||
.addQueryParameter("perPage", PER_PAGE_MANGA_TITLES.toString())
|
||||
|
||||
return GET(url.build(), headers)
|
||||
}
|
||||
|
||||
val payloadObj = HeanCmsQuerySearchPayloadDto(
|
||||
page = page,
|
||||
order = "desc",
|
||||
orderBy = "total_views",
|
||||
type = "Comic",
|
||||
)
|
||||
|
||||
val payload = json.encodeToString(payloadObj).toRequestBody(JSON_MEDIA_TYPE)
|
||||
|
||||
val apiHeaders = headersBuilder()
|
||||
.add("Accept", ACCEPT_JSON)
|
||||
.add("Content-Type", payload.contentType().toString())
|
||||
.build()
|
||||
|
||||
return POST("$apiUrl/series/querysearch", apiHeaders, payload)
|
||||
}
|
||||
|
||||
protected open fun parseAllTitles(result: List<HeanCmsSeriesDto>): Map<String, HeanCmsTitle> {
|
||||
return result
|
||||
.filter { it.type == "Comic" }
|
||||
.associateBy(
|
||||
keySelector = { it.slug.replace(TIMESTAMP_REGEX, "") },
|
||||
valueTransform = {
|
||||
HeanCmsTitle(
|
||||
slug = it.slug,
|
||||
thumbnailFileName = it.thumbnail,
|
||||
status = it.status?.toStatus() ?: SManga.UNKNOWN,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
|
||||
/**
|
||||
* Used to specify the strategy to use when fetching the slug for a manga.
|
||||
* This is needed because some sources change the slug periodically.
|
||||
* [NONE]: Use series_slug without changes.
|
||||
* [ID]: Use series_id to fetch the slug from the API.
|
||||
* IMPORTANT: [ID] is only available in the new query endpoint.
|
||||
* [FETCH_ALL]: Convert the slug to a permanent slug by removing the timestamp.
|
||||
* At extension start, all the slugs are fetched and stored in a map.
|
||||
*/
|
||||
enum class SlugStrategy {
|
||||
NONE, ID, FETCH_ALL
|
||||
}
|
||||
|
||||
private fun String.toPermSlugIfNeeded(): String {
|
||||
return if (slugStrategy != SlugStrategy.NONE) {
|
||||
this.replace(TIMESTAMP_REGEX, "")
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun getStatusList(): List<Status> = listOf(
|
||||
Status(intl.statusAll, "All"),
|
||||
Status(intl.statusOngoing, "Ongoing"),
|
||||
Status(intl.statusOnHiatus, "Hiatus"),
|
||||
Status(intl.statusDropped, "Dropped"),
|
||||
Status(intl["status_all"], "All"),
|
||||
Status(intl["status_ongoing"], "Ongoing"),
|
||||
Status(intl["status_onhiatus"], "Hiatus"),
|
||||
Status(intl["status_dropped"], "Dropped"),
|
||||
)
|
||||
|
||||
protected open fun getSortProperties(): List<SortProperty> = listOf(
|
||||
SortProperty(intl.sortByTitle, "title"),
|
||||
SortProperty(intl.sortByViews, "total_views"),
|
||||
SortProperty(intl.sortByLatest, "latest"),
|
||||
SortProperty(intl.sortByCreatedAt, "created_at"),
|
||||
SortProperty(intl["sort_by_title"], "title"),
|
||||
SortProperty(intl["sort_by_views"], "total_views"),
|
||||
SortProperty(intl["sort_by_latest"], "latest"),
|
||||
SortProperty(intl["sort_by_created_at"], "created_at"),
|
||||
)
|
||||
|
||||
protected open fun getGenreList(): List<Genre> = emptyList()
|
||||
@ -646,15 +327,24 @@ abstract class HeanCms(
|
||||
val genres = getGenreList()
|
||||
|
||||
val filters = listOfNotNull(
|
||||
Filter.Header(intl.filterWarning),
|
||||
StatusFilter(intl.statusFilterTitle, getStatusList()),
|
||||
SortByFilter(intl.sortByFilterTitle, getSortProperties()),
|
||||
GenreFilter(intl.genreFilterTitle, genres).takeIf { genres.isNotEmpty() },
|
||||
StatusFilter(intl["status_filter_title"], getStatusList()),
|
||||
SortByFilter(intl["sort_by_filter_title"], getSortProperties()),
|
||||
GenreFilter(intl["genre_filter_title"], genres).takeIf { genres.isNotEmpty() },
|
||||
)
|
||||
|
||||
return FilterList(filters)
|
||||
}
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
SwitchPreferenceCompat(screen.context).apply {
|
||||
key = SHOW_PAID_CHAPTERS_PREF
|
||||
title = intl["pref_show_paid_chapter_title"]
|
||||
summaryOn = intl["pref_show_paid_chapter_summary_on"]
|
||||
summaryOff = intl["pref_show_paid_chapter_summary_off"]
|
||||
setDefaultValue(SHOW_PAID_CHAPTERS_DEFAULT)
|
||||
}.also(screen::addPreference)
|
||||
}
|
||||
|
||||
protected inline fun <reified T> Response.parseAs(): T = use {
|
||||
it.body.string().parseAs()
|
||||
}
|
||||
@ -664,18 +354,6 @@ abstract class HeanCms(
|
||||
protected inline fun <reified R> List<*>.firstInstanceOrNull(): R? =
|
||||
filterIsInstance<R>().firstOrNull()
|
||||
|
||||
protected var SharedPreferences.slugMap: MutableMap<String, String>
|
||||
get() {
|
||||
val jsonMap = getString(PREF_URL_MAP_SLUG, "{}")!!
|
||||
val slugMap = runCatching { json.decodeFromString<Map<String, String>>(jsonMap) }
|
||||
return slugMap.getOrNull()?.toMutableMap() ?: mutableMapOf()
|
||||
}
|
||||
set(newSlugMap) {
|
||||
edit()
|
||||
.putString(PREF_URL_MAP_SLUG, json.encodeToString(newSlugMap))
|
||||
.apply()
|
||||
}
|
||||
|
||||
private val SharedPreferences.showPaidChapters: Boolean
|
||||
get() = getBoolean(SHOW_PAID_CHAPTERS_PREF, SHOW_PAID_CHAPTERS_DEFAULT)
|
||||
|
||||
@ -683,16 +361,10 @@ abstract class HeanCms(
|
||||
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 val JSON_MEDIA_TYPE = "application/json".toMediaType()
|
||||
|
||||
val TIMESTAMP_REGEX = """-\d{13}$""".toRegex()
|
||||
|
||||
private const val PER_PAGE_MANGA_TITLES = 10000
|
||||
private const val PER_PAGE_CHAPTERS = 1000
|
||||
|
||||
const val SEARCH_PREFIX = "slug:"
|
||||
|
||||
private const val PREF_URL_MAP_SLUG = "pref_url_map"
|
||||
|
||||
private const val SHOW_PAID_CHAPTERS_PREF = "pref_show_paid_chap"
|
||||
private const val SHOW_PAID_CHAPTERS_DEFAULT = false
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package eu.kanade.tachiyomi.multisrc.heancms
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.heancms.HeanCms.SlugStrategy
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import kotlinx.serialization.SerialName
|
||||
@ -9,59 +8,30 @@ import org.jsoup.Jsoup
|
||||
import java.text.SimpleDateFormat
|
||||
|
||||
@Serializable
|
||||
data class HeanCmsQuerySearchDto(
|
||||
class HeanCmsQuerySearchDto(
|
||||
val data: List<HeanCmsSeriesDto> = emptyList(),
|
||||
val meta: HeanCmsQuerySearchMetaDto? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class HeanCmsQuerySearchMetaDto(
|
||||
@SerialName("current_page") val currentPage: Int,
|
||||
@SerialName("last_page") val lastPage: Int,
|
||||
class HeanCmsQuerySearchMetaDto(
|
||||
@SerialName("current_page") private val currentPage: Int,
|
||||
@SerialName("last_page") private val lastPage: Int,
|
||||
) {
|
||||
|
||||
val hasNextPage: Boolean
|
||||
get() = currentPage < lastPage
|
||||
fun hasNextPage() = currentPage < lastPage
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class HeanCmsSearchDto(
|
||||
val description: String? = null,
|
||||
@SerialName("series_slug") var slug: String,
|
||||
@SerialName("series_type") val type: String,
|
||||
val title: String,
|
||||
val thumbnail: String? = null,
|
||||
) {
|
||||
|
||||
fun toSManga(
|
||||
apiUrl: String,
|
||||
coverPath: String,
|
||||
mangaSubDirectory: String,
|
||||
slugMap: Map<String, HeanCms.HeanCmsTitle>,
|
||||
slugStrategy: SlugStrategy,
|
||||
): SManga = SManga.create().apply {
|
||||
val slugOnly = slug.toPermSlugIfNeeded(slugStrategy)
|
||||
val thumbnailFileName = slugMap[slugOnly]?.thumbnailFileName
|
||||
title = this@HeanCmsSearchDto.title
|
||||
thumbnail_url = thumbnail?.toAbsoluteThumbnailUrl(apiUrl, coverPath)
|
||||
?: thumbnailFileName?.toAbsoluteThumbnailUrl(apiUrl, coverPath)
|
||||
url = "/$mangaSubDirectory/$slugOnly"
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class HeanCmsSeriesDto(
|
||||
class HeanCmsSeriesDto(
|
||||
val id: Int,
|
||||
@SerialName("series_slug") val slug: String,
|
||||
@SerialName("series_type") val type: String = "Comic",
|
||||
val author: String? = null,
|
||||
val description: String? = null,
|
||||
val studio: String? = null,
|
||||
val status: String? = null,
|
||||
val thumbnail: String,
|
||||
val title: String,
|
||||
val tags: List<HeanCmsTagDto>? = emptyList(),
|
||||
val chapters: List<HeanCmsChapterDto>? = emptyList(),
|
||||
private val author: String? = null,
|
||||
private val description: String? = null,
|
||||
private val studio: String? = null,
|
||||
private val status: String? = null,
|
||||
private val thumbnail: String,
|
||||
private val title: String,
|
||||
private val tags: List<HeanCmsTagDto>? = emptyList(),
|
||||
val seasons: List<HeanCmsSeasonsDto>? = emptyList(),
|
||||
) {
|
||||
|
||||
@ -69,10 +39,8 @@ data class HeanCmsSeriesDto(
|
||||
apiUrl: String,
|
||||
coverPath: String,
|
||||
mangaSubDirectory: String,
|
||||
slugStrategy: SlugStrategy,
|
||||
): SManga = SManga.create().apply {
|
||||
val descriptionBody = this@HeanCmsSeriesDto.description?.let(Jsoup::parseBodyFragment)
|
||||
val slugOnly = slug.toPermSlugIfNeeded(slugStrategy)
|
||||
|
||||
title = this@HeanCmsSeriesDto.title
|
||||
author = this@HeanCmsSeriesDto.author?.trim()
|
||||
@ -86,89 +54,84 @@ data class HeanCmsSeriesDto(
|
||||
thumbnail_url = thumbnail.ifEmpty { null }
|
||||
?.toAbsoluteThumbnailUrl(apiUrl, coverPath)
|
||||
status = this@HeanCmsSeriesDto.status?.toStatus() ?: SManga.UNKNOWN
|
||||
url = if (slugStrategy != SlugStrategy.NONE) {
|
||||
"/$mangaSubDirectory/$slugOnly#$id"
|
||||
} else {
|
||||
"/$mangaSubDirectory/$slug"
|
||||
}
|
||||
url = "/$mangaSubDirectory/$slug#$id"
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class HeanCmsSeasonsDto(
|
||||
val index: Int,
|
||||
class HeanCmsSeasonsDto(
|
||||
val chapters: List<HeanCmsChapterDto>? = emptyList(),
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class HeanCmsTagDto(val name: String)
|
||||
class HeanCmsTagDto(val name: String)
|
||||
|
||||
@Serializable
|
||||
data class HeanCmsChapterDto(
|
||||
val id: Int,
|
||||
@SerialName("chapter_name") val name: String,
|
||||
@SerialName("chapter_slug") val slug: String,
|
||||
val index: String,
|
||||
@SerialName("created_at") val createdAt: String,
|
||||
class HeanCmsChapterPayloadDto(
|
||||
val data: List<HeanCmsChapterDto>,
|
||||
val meta: HeanCmsChapterMetaDto,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class HeanCmsChapterDto(
|
||||
private val id: Int,
|
||||
@SerialName("chapter_name") private val name: String,
|
||||
@SerialName("chapter_slug") private val slug: String,
|
||||
@SerialName("created_at") private val createdAt: String,
|
||||
val price: Int? = null,
|
||||
) {
|
||||
fun toSChapter(
|
||||
seriesSlug: String,
|
||||
mangaSubDirectory: String,
|
||||
dateFormat: SimpleDateFormat,
|
||||
slugStrategy: SlugStrategy,
|
||||
): SChapter = SChapter.create().apply {
|
||||
val seriesSlugOnly = seriesSlug.toPermSlugIfNeeded(slugStrategy)
|
||||
name = this@HeanCmsChapterDto.name.trim()
|
||||
|
||||
if (price != 0) {
|
||||
name += " \uD83D\uDD12"
|
||||
}
|
||||
|
||||
date_upload = runCatching { dateFormat.parse(createdAt)?.time }
|
||||
.getOrNull() ?: 0L
|
||||
date_upload = try {
|
||||
dateFormat.parse(createdAt)?.time ?: 0L
|
||||
} catch (_: Exception) {
|
||||
0L
|
||||
}
|
||||
|
||||
val paidStatus = if (price != 0 && price != null) "-paid" else ""
|
||||
|
||||
url = "/$mangaSubDirectory/$seriesSlugOnly/$slug#$id$paidStatus"
|
||||
url = "/$mangaSubDirectory/$seriesSlug/$slug#$id"
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class HeanCmsReaderDto(
|
||||
val content: HeanCmsReaderContentDto? = null,
|
||||
class HeanCmsChapterMetaDto(
|
||||
@SerialName("current_page") private val currentPage: Int,
|
||||
@SerialName("last_page") private val lastPage: Int,
|
||||
) {
|
||||
fun hasNextPage() = currentPage < lastPage
|
||||
}
|
||||
|
||||
@Serializable
|
||||
class HeanCmsPagePayloadDto(
|
||||
val chapter: HeanCmsPageDto,
|
||||
private val paywall: Boolean = false,
|
||||
val data: List<String>? = emptyList(),
|
||||
) {
|
||||
fun isPaywalled() = paywall
|
||||
}
|
||||
|
||||
@Serializable
|
||||
class HeanCmsPageDto(
|
||||
@SerialName("chapter_data") val chapterData: HeanCmsPageDataDto?,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class HeanCmsReaderContentDto(
|
||||
class HeanCmsPageDataDto(
|
||||
val images: List<String>? = emptyList(),
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class HeanCmsQuerySearchPayloadDto(
|
||||
val order: String,
|
||||
val page: Int,
|
||||
@SerialName("order_by") val orderBy: String,
|
||||
@SerialName("series_status") val status: String? = null,
|
||||
@SerialName("series_type") val type: String,
|
||||
@SerialName("tags_ids") val tagIds: List<Int> = emptyList(),
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class HeanCmsSearchPayloadDto(val term: String)
|
||||
|
||||
private fun String.toAbsoluteThumbnailUrl(apiUrl: String, coverPath: String): String {
|
||||
return if (startsWith("https://")) this else "$apiUrl/$coverPath$this"
|
||||
}
|
||||
|
||||
private fun String.toPermSlugIfNeeded(slugStrategy: SlugStrategy): String {
|
||||
return if (slugStrategy != SlugStrategy.NONE) {
|
||||
this.replace(HeanCms.TIMESTAMP_REGEX, "")
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
fun String.toStatus(): Int = when (this) {
|
||||
"Ongoing" -> SManga.ONGOING
|
||||
"Hiatus" -> SManga.ON_HIATUS
|
||||
|
@ -1,124 +0,0 @@
|
||||
package eu.kanade.tachiyomi.multisrc.heancms
|
||||
|
||||
class HeanCmsIntl(lang: String) {
|
||||
|
||||
val availableLang: String = if (lang in AVAILABLE_LANGS) lang else ENGLISH
|
||||
|
||||
val genreFilterTitle: String = when (availableLang) {
|
||||
BRAZILIAN_PORTUGUESE -> "Gêneros"
|
||||
SPANISH -> "Géneros"
|
||||
else -> "Genres"
|
||||
}
|
||||
|
||||
val statusFilterTitle: String = when (availableLang) {
|
||||
BRAZILIAN_PORTUGUESE -> "Estado"
|
||||
SPANISH -> "Estado"
|
||||
else -> "Status"
|
||||
}
|
||||
|
||||
val statusAll: String = when (availableLang) {
|
||||
BRAZILIAN_PORTUGUESE -> "Todos"
|
||||
SPANISH -> "Todos"
|
||||
else -> "All"
|
||||
}
|
||||
|
||||
val statusOngoing: String = when (availableLang) {
|
||||
BRAZILIAN_PORTUGUESE -> "Em andamento"
|
||||
SPANISH -> "En curso"
|
||||
else -> "Ongoing"
|
||||
}
|
||||
|
||||
val statusOnHiatus: String = when (availableLang) {
|
||||
BRAZILIAN_PORTUGUESE -> "Em hiato"
|
||||
SPANISH -> "En hiatus"
|
||||
else -> "On Hiatus"
|
||||
}
|
||||
|
||||
val statusDropped: String = when (availableLang) {
|
||||
BRAZILIAN_PORTUGUESE -> "Cancelada"
|
||||
SPANISH -> "Abandonada"
|
||||
else -> "Dropped"
|
||||
}
|
||||
|
||||
val sortByFilterTitle: String = when (availableLang) {
|
||||
BRAZILIAN_PORTUGUESE -> "Ordenar por"
|
||||
SPANISH -> "Ordenar por"
|
||||
else -> "Sort by"
|
||||
}
|
||||
|
||||
val sortByTitle: String = when (availableLang) {
|
||||
BRAZILIAN_PORTUGUESE -> "Título"
|
||||
SPANISH -> "Titulo"
|
||||
else -> "Title"
|
||||
}
|
||||
|
||||
val sortByViews: String = when (availableLang) {
|
||||
BRAZILIAN_PORTUGUESE -> "Visualizações"
|
||||
SPANISH -> "Número de vistas"
|
||||
else -> "Views"
|
||||
}
|
||||
|
||||
val sortByLatest: String = when (availableLang) {
|
||||
BRAZILIAN_PORTUGUESE -> "Recentes"
|
||||
SPANISH -> "Recientes"
|
||||
else -> "Latest"
|
||||
}
|
||||
|
||||
val sortByCreatedAt: String = when (availableLang) {
|
||||
BRAZILIAN_PORTUGUESE -> "Data de criação"
|
||||
SPANISH -> "Fecha de creación"
|
||||
else -> "Created at"
|
||||
}
|
||||
|
||||
val filterWarning: String = when (availableLang) {
|
||||
BRAZILIAN_PORTUGUESE -> "Os filtros serão ignorados se a busca não estiver vazia."
|
||||
SPANISH -> "Los filtros serán ignorados si la búsqueda no está vacía."
|
||||
else -> "Filters will be ignored if the search is not empty."
|
||||
}
|
||||
|
||||
val prefShowPaidChapterTitle: String = when (availableLang) {
|
||||
SPANISH -> "Mostrar capítulos de pago"
|
||||
else -> "Display paid chapters"
|
||||
}
|
||||
|
||||
val prefShowPaidChapterSummaryOn: String = when (availableLang) {
|
||||
SPANISH -> "Se mostrarán capítulos de pago. Deberá iniciar sesión"
|
||||
else -> "Paid chapters will appear. A login might be needed!"
|
||||
}
|
||||
|
||||
val prefShowPaidChapterSummaryOff: String = when (availableLang) {
|
||||
SPANISH -> "Solo se mostrarán los capítulos gratuitos"
|
||||
else -> "Only free chapters will be displayed."
|
||||
}
|
||||
|
||||
val paidChapterError: String = when (availableLang) {
|
||||
SPANISH -> "Capítulo no disponible. Debe iniciar sesión en Webview y tener el capítulo comprado."
|
||||
else -> "Paid chapter unavailable.\nA login/purchase might be needed (using webview)."
|
||||
}
|
||||
|
||||
fun urlChangedError(sourceName: String): String = when (availableLang) {
|
||||
BRAZILIAN_PORTUGUESE ->
|
||||
"A URL da série mudou. Migre de $sourceName " +
|
||||
"para $sourceName para atualizar a URL."
|
||||
SPANISH ->
|
||||
"La URL de la serie ha cambiado. Migre de $sourceName a " +
|
||||
"$sourceName para actualizar la URL."
|
||||
else ->
|
||||
"The URL of the series has changed. Migrate from $sourceName " +
|
||||
"to $sourceName to update the URL."
|
||||
}
|
||||
|
||||
val idNotFoundError: String = when (availableLang) {
|
||||
BRAZILIAN_PORTUGUESE -> "Falha ao obter o ID do slug: "
|
||||
SPANISH -> "No se pudo encontrar el ID para: "
|
||||
else -> "Failed to get the ID for slug: "
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val BRAZILIAN_PORTUGUESE = "pt-BR"
|
||||
const val ENGLISH = "en"
|
||||
const val SPANISH = "es"
|
||||
|
||||
val AVAILABLE_LANGS = arrayOf(BRAZILIAN_PORTUGUESE, ENGLISH, SPANISH)
|
||||
}
|
||||
}
|
@ -11,12 +11,10 @@ class OmegaScans : HeanCms("Omega Scans", "https://omegascans.org", "en") {
|
||||
.rateLimitHost(apiUrl.toHttpUrl(), 1)
|
||||
.build()
|
||||
|
||||
override val useNewQueryEndpoint = true
|
||||
|
||||
// Site changed from MangaThemesia to HeanCms.
|
||||
override val versionId = 2
|
||||
|
||||
override val coverPath = ""
|
||||
override val useNewChapterEndpoint = true
|
||||
|
||||
override fun getGenreList() = listOf(
|
||||
Genre("Romance", 1),
|
||||
|
@ -17,8 +17,6 @@ class TempleScan : HeanCms(
|
||||
.rateLimit(1)
|
||||
.build()
|
||||
|
||||
override val useNewQueryEndpoint = true
|
||||
override val coverPath = ""
|
||||
override val mangaSubDirectory = "comic"
|
||||
|
||||
override fun getGenreList() = listOf(
|
||||
|
@ -6,12 +6,18 @@ import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
|
||||
class PerfScan : HeanCms("Perf Scan", "https://perf-scan.fr", "fr") {
|
||||
|
||||
override val client: OkHttpClient = super.client.newBuilder()
|
||||
.rateLimitHost(apiUrl.toHttpUrl(), 1, 2)
|
||||
.build()
|
||||
|
||||
override val coverPath: String = ""
|
||||
override val useNewQueryEndpoint = true
|
||||
init {
|
||||
preferences.run {
|
||||
if (contains("pref_url_map")) {
|
||||
edit().remove("pref_url_map").apply()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val slugStrategy = SlugStrategy.ID
|
||||
override val useNewChapterEndpoint = true
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user