HeanCMS: Add fetchStrategy (#17320)

* Add fetchStrategy

* Update multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/heancms/HeanCms.kt

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>

* Update multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/heancms/HeanCms.kt

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>

* Update multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/heancms/HeanCms.kt

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>

* Remove YugenMangas companion object

* Lol

* Update multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/heancms/HeanCms.kt

Co-authored-by: Alessandro Jean <14254807+alessandrojean@users.noreply.github.com>

* Update multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/heancms/HeanCms.kt

Co-authored-by: Alessandro Jean <14254807+alessandrojean@users.noreply.github.com>

* Update multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/heancms/HeanCms.kt

Co-authored-by: Alessandro Jean <14254807+alessandrojean@users.noreply.github.com>

* Update multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/heancms/HeanCms.kt

Co-authored-by: Alessandro Jean <14254807+alessandrojean@users.noreply.github.com>

* Update multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/heancms/HeanCms.kt

Co-authored-by: Alessandro Jean <14254807+alessandrojean@users.noreply.github.com>

* Update multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/heancms/HeanCms.kt

Co-authored-by: Alessandro Jean <14254807+alessandrojean@users.noreply.github.com>

* Fixes

* get slug directly instead convert to SManga

* change new result function

* remove unnecessary var

* unused imports

* Exact search NOT RETURN LATEST TIMESTAMP O.o

* parse response directly

Co-authored-by: Alessandro Jean <14254807+alessandrojean@users.noreply.github.com>

* change search query

---------

Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
Co-authored-by: Alessandro Jean <14254807+alessandrojean@users.noreply.github.com>
This commit is contained in:
Rolando Lecca 2023-07-31 12:56:44 -05:00 committed by GitHub
parent 31ae3fc43a
commit f9834fcca0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 123 additions and 65 deletions

View File

@ -21,7 +21,7 @@ class ReaperScans : HeanCms(
// Site changed from Madara to HeanCms. // Site changed from Madara to HeanCms.
override val versionId = 2 override val versionId = 2
override val fetchAllTitles = true override val fetchAllTitlesStrategy = FetchAllStrategy.SEARCH_ALL
override val coverPath: String = "" override val coverPath: String = ""

View File

@ -1,16 +1,8 @@
package eu.kanade.tachiyomi.extension.es.yugenmangas package eu.kanade.tachiyomi.extension.es.yugenmangas
import android.app.Application
import android.content.SharedPreferences
import android.widget.Toast
import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreferenceCompat
import eu.kanade.tachiyomi.multisrc.heancms.Genre import eu.kanade.tachiyomi.multisrc.heancms.Genre
import eu.kanade.tachiyomi.multisrc.heancms.HeanCms import eu.kanade.tachiyomi.multisrc.heancms.HeanCms
import eu.kanade.tachiyomi.network.interceptor.rateLimit import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.ConfigurableSource
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.TimeZone import java.util.TimeZone
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -21,17 +13,12 @@ class YugenMangas :
"https://yugenmangas.net", "https://yugenmangas.net",
"es", "es",
"https://api.yugenmangas.net", "https://api.yugenmangas.net",
), ) {
ConfigurableSource {
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
// Site changed from Madara to HeanCms. // Site changed from Madara to HeanCms.
override val versionId = 2 override val versionId = 2
override val fetchAllTitles = getFetchAllSeriesPref() override val fetchAllTitlesStrategy = FetchAllStrategy.SEARCH_EACH
override val client = super.client.newBuilder() override val client = super.client.newBuilder()
.connectTimeout(60, TimeUnit.SECONDS) .connectTimeout(60, TimeUnit.SECONDS)
@ -45,26 +32,6 @@ class YugenMangas :
timeZone = TimeZone.getTimeZone("UTC") timeZone = TimeZone.getTimeZone("UTC")
} }
private fun getFetchAllSeriesPref(): Boolean {
return preferences.getBoolean(FETCH_ALL_SERIES_PREF, FETCH_ALL_SERIES_DEFAULT_VALUE)
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
val fetchAllSeriesPreference = SwitchPreferenceCompat(screen.context).apply {
key = FETCH_ALL_SERIES_PREF
title = FETCH_ALL_SERIES_TITLE
summary = FETCH_ALL_SERIES_SUMMARY
setDefaultValue(FETCH_ALL_SERIES_DEFAULT_VALUE)
setOnPreferenceChangeListener { _, newValue ->
Toast.makeText(screen.context, "Reinicia la app para aplicar los cambios.", Toast.LENGTH_LONG).show()
true
}
}
screen.addPreference(fetchAllSeriesPreference)
}
override fun getGenreList(): List<Genre> = listOf( override fun getGenreList(): List<Genre> = listOf(
Genre("+18", 1), Genre("+18", 1),
Genre("Acción", 36), Genre("Acción", 36),
@ -115,14 +82,4 @@ class YugenMangas :
Genre("Yaoi", 43), Genre("Yaoi", 43),
Genre("Yuri", 44), Genre("Yuri", 44),
) )
companion object {
private const val FETCH_ALL_SERIES_PREF = "fetchAllSeriesPref"
private const val FETCH_ALL_SERIES_TITLE = "Buscar todas las series"
private const val FETCH_ALL_SERIES_SUMMARY = "Busca las URLs actuales de las series. " +
"Habilitar esta opción evita la necesidad de migrar, " +
"pero puede llevar tiempo dependiendo del número total de series. \n" +
"Tendrá que migrar cada vez que cambie esta opción."
private const val FETCH_ALL_SERIES_DEFAULT_VALUE = true
}
} }

View File

@ -1,5 +1,7 @@
package eu.kanade.tachiyomi.multisrc.heancms package eu.kanade.tachiyomi.multisrc.heancms
import android.app.Application
import android.content.SharedPreferences
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
@ -19,6 +21,8 @@ import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response import okhttp3.Response
import rx.Observable import rx.Observable
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
@ -29,6 +33,10 @@ abstract class HeanCms(
protected val apiUrl: String = baseUrl.replace("://", "://api."), protected val apiUrl: String = baseUrl.replace("://", "://api."),
) : HttpSource() { ) : HttpSource() {
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
override val supportsLatest = true override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient override val client: OkHttpClient = network.cloudflareClient
@ -45,7 +53,7 @@ abstract class HeanCms(
protected val intl by lazy { HeanCmsIntl(lang) } protected val intl by lazy { HeanCmsIntl(lang) }
protected open val fetchAllTitles: Boolean = false protected open val fetchAllTitlesStrategy = FetchAllStrategy.NONE
protected open val coverPath: String = "cover/" protected open val coverPath: String = "cover/"
@ -81,7 +89,7 @@ abstract class HeanCms(
if (json.startsWith("{")) { if (json.startsWith("{")) {
val result = json.parseAs<HeanCmsQuerySearchDto>() val result = json.parseAs<HeanCmsQuerySearchDto>()
val mangaList = result.data.map { it.toSManga(apiUrl, coverPath, fetchAllTitles) } val mangaList = result.data.map { it.toSManga(apiUrl, coverPath, fetchAllTitlesStrategy) }
fetchAllTitles() fetchAllTitles()
@ -89,7 +97,7 @@ abstract class HeanCms(
} }
val mangaList = json.parseAs<List<HeanCmsSeriesDto>>() val mangaList = json.parseAs<List<HeanCmsSeriesDto>>()
.map { it.toSManga(apiUrl, coverPath, fetchAllTitles) } .map { it.toSManga(apiUrl, coverPath, fetchAllTitlesStrategy) }
fetchAllTitles() fetchAllTitles()
@ -179,14 +187,14 @@ abstract class HeanCms(
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 { it.toSManga(apiUrl, coverPath, seriesSlugMap.orEmpty(), fetchAllTitles) } .map { it.toSManga(apiUrl, coverPath, seriesSlugMap.orEmpty(), fetchAllTitlesStrategy) }
return MangasPage(mangaList, false) return MangasPage(mangaList, false)
} }
if (json.startsWith("{")) { if (json.startsWith("{")) {
val result = json.parseAs<HeanCmsQuerySearchDto>() val result = json.parseAs<HeanCmsQuerySearchDto>()
val mangaList = result.data.map { it.toSManga(apiUrl, coverPath, fetchAllTitles) } val mangaList = result.data.map { it.toSManga(apiUrl, coverPath, fetchAllTitlesStrategy) }
fetchAllTitles() fetchAllTitles()
@ -194,7 +202,7 @@ abstract class HeanCms(
} }
val mangaList = json.parseAs<List<HeanCmsSeriesDto>>() val mangaList = json.parseAs<List<HeanCmsSeriesDto>>()
.map { it.toSManga(apiUrl, coverPath, fetchAllTitles) } .map { it.toSManga(apiUrl, coverPath, fetchAllTitlesStrategy) }
fetchAllTitles() fetchAllTitles()
@ -206,12 +214,38 @@ abstract class HeanCms(
.substringAfterLast("/") .substringAfterLast("/")
.replace(TIMESTAMP_REGEX, "") .replace(TIMESTAMP_REGEX, "")
val currentSlug = seriesSlugMap?.get(seriesSlug)?.slug ?: manga.url.substringAfterLast("/") val currentSlug = if (fetchAllTitlesStrategy == FetchAllStrategy.SEARCH_EACH) {
preferences.slugMap[seriesSlug] ?: manga.url.substringAfterLast("/")
} else {
seriesSlugMap?.get(seriesSlug)?.slug ?: manga.url.substringAfterLast("/")
}
return "$baseUrl/series/$currentSlug" return "$baseUrl/series/$currentSlug"
} }
override fun mangaDetailsRequest(manga: SManga): Request { override fun mangaDetailsRequest(manga: SManga): Request {
if (fetchAllTitlesStrategy == FetchAllStrategy.SEARCH_EACH) {
val searchQuery = manga.title.trim()
.replaceAfterLast(" ", "").trim()
.let {
if (it.length > 2) it.dropLast(1).trim() else it
}
val searchPayloadObj = HeanCmsSearchPayloadDto(searchQuery)
val searchPayload = json.encodeToString(searchPayloadObj)
.toRequestBody(JSON_MEDIA_TYPE)
val apiHeaders = headersBuilder()
.add("Accept", ACCEPT_JSON)
.add("Content-Type", searchPayload.contentType().toString())
.build()
val mangaSlug = manga.url
.substringAfterLast("/")
.replace(TIMESTAMP_REGEX, "")
return POST("$apiUrl/series/search#$mangaSlug", apiHeaders, searchPayload)
}
val seriesSlug = manga.url val seriesSlug = manga.url
.substringAfterLast("/") .substringAfterLast("/")
.replace(TIMESTAMP_REGEX, "") .replace(TIMESTAMP_REGEX, "")
@ -230,30 +264,72 @@ abstract class HeanCms(
} }
override fun mangaDetailsParse(response: Response): SManga { override fun mangaDetailsParse(response: Response): SManga {
val result = runCatching { response.parseAs<HeanCmsSeriesDto>() } val mangaStatus = response.request.url.fragment?.toIntOrNull() ?: SManga.UNKNOWN
val seriesDetails = result.getOrNull()?.toSManga(apiUrl, coverPath, fetchAllTitles)
val result = runCatching {
if (fetchAllTitlesStrategy == FetchAllStrategy.SEARCH_EACH) {
val originalSlug = response.request.url.fragment!!
val searchResult = response.parseAs<List<HeanCmsSearchDto>>()
searchResultToSeries(originalSlug, searchResult)
} else {
response.parseAs<HeanCmsSeriesDto>()
}
}
val seriesDetails = result.getOrNull()?.toSManga(apiUrl, coverPath, fetchAllTitlesStrategy)
?: throw Exception(intl.urlChangedError(name)) ?: throw Exception(intl.urlChangedError(name))
return seriesDetails.apply { return seriesDetails.apply {
status = status.takeUnless { it == SManga.UNKNOWN } status = status.takeUnless { it == SManga.UNKNOWN }
?: response.request.url.fragment?.toIntOrNull() ?: SManga.UNKNOWN ?: mangaStatus
} }
} }
override fun chapterListRequest(manga: SManga): Request = mangaDetailsRequest(manga) override fun chapterListRequest(manga: SManga): Request = mangaDetailsRequest(manga)
override fun chapterListParse(response: Response): List<SChapter> { override fun chapterListParse(response: Response): List<SChapter> {
val result = response.parseAs<HeanCmsSeriesDto>() val result = if (fetchAllTitlesStrategy == FetchAllStrategy.SEARCH_EACH) {
val seriesSlug = response.request.url.pathSegments.last() val originalSlug = response.request.url.fragment!!
val searchResult = response.parseAs<List<HeanCmsSearchDto>>()
searchResultToSeries(originalSlug, searchResult)
} else {
response.parseAs<HeanCmsSeriesDto>()
}
val currentTimestamp = System.currentTimeMillis() val currentTimestamp = System.currentTimeMillis()
return result.chapters.orEmpty() return result.chapters.orEmpty()
.filterNot { it.price == 1 } .filterNot { it.price == 1 }
.map { it.toSChapter(seriesSlug, dateFormat) } .map { it.toSChapter(result.slug, dateFormat) }
.filter { it.date_upload <= currentTimestamp } .filter { it.date_upload <= currentTimestamp }
.reversed() .reversed()
} }
private fun searchResultToSeries(originalSlug: String, searchResult: List<HeanCmsSearchDto>): HeanCmsSeriesDto {
val mangaSlug = searchResult
.filter { it.type == "Comic" }
.map { it.slug }
.find { it.replace(TIMESTAMP_REGEX, "") == originalSlug }
?: throw Exception(intl.urlChangedError(name))
val apiHeaders = headersBuilder()
.add("Accept", ACCEPT_JSON)
.build()
val detailsRequest = GET("$apiUrl/series/$mangaSlug", apiHeaders)
val result = client.newCall(detailsRequest).execute()
.parseAs<HeanCmsSeriesDto>()
val permSlug = result.slug
.substringAfterLast("/")
.replace(TIMESTAMP_REGEX, "")
preferences.slugMap = preferences.slugMap.toMutableMap()
.also { it[permSlug] = result.slug.substringAfterLast("/") }
return result
}
override fun getChapterUrl(chapter: SChapter): String = baseUrl + chapter.url override fun getChapterUrl(chapter: SChapter): String = baseUrl + chapter.url
override fun pageListRequest(chapter: SChapter): Request { override fun pageListRequest(chapter: SChapter): Request {
@ -309,7 +385,7 @@ abstract class HeanCms(
protected open fun getGenreList(): List<Genre> = emptyList() protected open fun getGenreList(): List<Genre> = emptyList()
protected open fun fetchAllTitles() { protected open fun fetchAllTitles() {
if (!seriesSlugMap.isNullOrEmpty() || !fetchAllTitles) { if (!seriesSlugMap.isNullOrEmpty() || fetchAllTitlesStrategy != FetchAllStrategy.SEARCH_ALL) {
return return
} }
@ -358,6 +434,18 @@ abstract class HeanCms(
return POST("$apiUrl/series/querysearch", apiHeaders, payload) return POST("$apiUrl/series/querysearch", apiHeaders, payload)
} }
protected var SharedPreferences.slugMap: MutableMap<String, String>
get() {
val jsonMap = getString(PREF_URL_MAP, "{}")!!
val slugMap = runCatching { json.decodeFromString<Map<String, String>>(jsonMap) }
return slugMap.getOrNull()?.toMutableMap() ?: mutableMapOf()
}
set(newSlugMap) {
edit()
.putString(PREF_URL_MAP, json.encodeToString(newSlugMap))
.commit()
}
protected open fun parseAllTitles(result: List<HeanCmsSeriesDto>): Map<String, HeanCmsTitle> { protected open fun parseAllTitles(result: List<HeanCmsSeriesDto>): Map<String, HeanCmsTitle> {
return result return result
.filter { it.type == "Comic" } .filter { it.type == "Comic" }
@ -401,6 +489,12 @@ abstract class HeanCms(
*/ */
data class HeanCmsTitle(val slug: String, val thumbnailFileName: String, val status: Int) data class HeanCmsTitle(val slug: String, val thumbnailFileName: String, val status: Int)
enum class FetchAllStrategy {
NONE,
SEARCH_EACH,
SEARCH_ALL,
}
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, */*"
@ -410,5 +504,7 @@ abstract class HeanCms(
val TIMESTAMP_REGEX = "-\\d+$".toRegex() val TIMESTAMP_REGEX = "-\\d+$".toRegex()
const val SEARCH_PREFIX = "slug:" const val SEARCH_PREFIX = "slug:"
private const val PREF_URL_MAP = "pref_url_map"
} }
} }

View File

@ -1,5 +1,6 @@
package eu.kanade.tachiyomi.multisrc.heancms package eu.kanade.tachiyomi.multisrc.heancms
import eu.kanade.tachiyomi.multisrc.heancms.HeanCms.FetchAllStrategy
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 kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
@ -36,9 +37,9 @@ data class HeanCmsSearchDto(
apiUrl: String, apiUrl: String,
coverPath: String, coverPath: String,
slugMap: Map<String, HeanCms.HeanCmsTitle>, slugMap: Map<String, HeanCms.HeanCmsTitle>,
usePermSlug: Boolean, fetchStrategy: FetchAllStrategy = FetchAllStrategy.NONE,
): SManga = SManga.create().apply { ): SManga = SManga.create().apply {
val slug = if (!usePermSlug) slug else slug.replace(HeanCms.TIMESTAMP_REGEX, "") val slug = if (fetchStrategy == FetchAllStrategy.NONE) slug else slug.replace(HeanCms.TIMESTAMP_REGEX, "")
val thumbnailFileName = slugMap[slug]?.thumbnailFileName val thumbnailFileName = slugMap[slug]?.thumbnailFileName
title = this@HeanCmsSearchDto.title title = this@HeanCmsSearchDto.title
@ -63,9 +64,13 @@ data class HeanCmsSeriesDto(
val chapters: List<HeanCmsChapterDto>? = emptyList(), val chapters: List<HeanCmsChapterDto>? = emptyList(),
) { ) {
fun toSManga(apiUrl: String, coverPath: String, usePermSlug: Boolean): SManga = SManga.create().apply { fun toSManga(
apiUrl: String,
coverPath: String,
fetchStrategy: FetchAllStrategy = FetchAllStrategy.NONE,
): SManga = SManga.create().apply {
val descriptionBody = this@HeanCmsSeriesDto.description?.let(Jsoup::parseBodyFragment) val descriptionBody = this@HeanCmsSeriesDto.description?.let(Jsoup::parseBodyFragment)
val slug = if (!usePermSlug) slug else slug.replace(HeanCms.TIMESTAMP_REGEX, "") val slug = if (fetchStrategy == FetchAllStrategy.NONE) slug else slug.replace(HeanCms.TIMESTAMP_REGEX, "")
title = this@HeanCmsSeriesDto.title title = this@HeanCmsSeriesDto.title
author = this@HeanCmsSeriesDto.author?.trim() author = this@HeanCmsSeriesDto.author?.trim()

View File

@ -9,7 +9,7 @@ class HeanCmsGenerator : ThemeSourceGenerator {
override val themeClass = "HeanCms" override val themeClass = "HeanCms"
override val baseVersionCode: Int = 14 override val baseVersionCode: Int = 15
override val sources = listOf( override val sources = listOf(
SingleLang("Glorious Scan", "https://gloriousscan.com", "pt-BR", overrideVersionCode = 17), SingleLang("Glorious Scan", "https://gloriousscan.com", "pt-BR", overrideVersionCode = 17),