using search to enable all extensions’ related mangas

This commit is contained in:
Cuong M. Tran 2024-04-25 16:25:40 +07:00
parent 89f8723d7e
commit 0cf256ccce
No known key found for this signature in database
GPG key ID: 733AA7624B9315C2
4 changed files with 74 additions and 19 deletions

View file

@ -1109,7 +1109,7 @@ class MangaScreenModel(
try { try {
withIOContext { withIOContext {
if (state.source !is MergedSource) { if (state.source !is MergedSource) {
val relatedMangas = if (state.source.supportsRelatedMangas) val relatedMangas = if (true)//state.source.supportsRelatedMangas)
withIOContext { withIOContext {
state.source.getRelatedMangaList(state.manga.toSManga()) state.source.getRelatedMangaList(state.manga.toSManga())
.map { .map {

View file

@ -49,13 +49,6 @@ interface Source {
} }
// KMK --> // KMK -->
/**
* Whether the source has support for related mangas.
*
* @since extensions-lib 1.6
*/
val supportsRelatedMangas: Boolean get() = false
/** /**
* Get all the available related mangas for a manga. * Get all the available related mangas for a manga.
* *

View file

@ -16,6 +16,10 @@ import eu.kanade.tachiyomi.source.model.SManga
import exh.log.maybeInjectEHLogger import exh.log.maybeInjectEHLogger
import exh.pref.DelegateSourcePreferences import exh.pref.DelegateSourcePreferences
import exh.source.DelegatedHttpSource import exh.source.DelegatedHttpSource
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import okhttp3.Headers import okhttp3.Headers
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
@ -27,6 +31,7 @@ import uy.kohesive.injekt.api.get
import java.net.URI import java.net.URI
import java.net.URISyntaxException import java.net.URISyntaxException
import java.security.MessageDigest import java.security.MessageDigest
import java.util.Locale
/** /**
* A simple implementation for sources from a website. * A simple implementation for sources from a website.
@ -275,6 +280,14 @@ abstract class HttpSource : CatalogueSource {
protected abstract fun mangaDetailsParse(response: Response): SManga protected abstract fun mangaDetailsParse(response: Response): SManga
// KMK --> // KMK -->
/**
* Whether parsing related mangas in manga page or extension provide custom related mangas request (true)
* or using search keyword to search for related mangas (false - default)
* @default false
* @since extensions-lib 1.6
*/
protected open val supportsRelatedMangas: Boolean get() = false
/** /**
* Get all the available related mangas for a manga. * Get all the available related mangas for a manga.
* Normally it's not needed to override this method. * Normally it's not needed to override this method.
@ -285,9 +298,60 @@ abstract class HttpSource : CatalogueSource {
*/ */
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
override suspend fun getRelatedMangaList(manga: SManga): List<SManga> { override suspend fun getRelatedMangaList(manga: SManga): List<SManga> {
return fetchRelatedMangaList(manga).awaitSingle() return if (supportsRelatedMangas)
fetchRelatedMangaList(manga).awaitSingle()
else
return fetchRelatedMangaListBySearch(manga)
} }
/**
* Fetch related mangas by searching for each keywords from manga's title
*/
protected open suspend fun fetchRelatedMangaListBySearch(manga: SManga): List<SManga> {
fun String.stripKeyword(): List<String> {
val regexWhitespace = Regex("\\s+")
// remove special character
return replace(Regex("[\\[(!~@#$%^&*|,?:\"<>)\\]]"), " ")
.split(regexWhitespace)
.map {
// remove number only
it.replace(Regex("^\\d+$"), "")
.lowercase(Locale.getDefault())
}
// exclude single character
.filter { it.length > 1 }
}
val scope = CoroutineScope(Dispatchers.IO)
val words = HashSet<String>()
words += manga.title.stripKeyword()
manga.originalTitle.let { title ->
words += title.stripKeyword()
}
if (words.isEmpty()) {
return emptyList()
}
val results = words.map { keyword ->
scope.async {
client.newCall(searchMangaRequest(0, keyword, FilterList()))
.execute()
.let { response ->
searchMangaParse(response).mangas
.filter {
it.url != manga.url &&
it.title.lowercase().contains(keyword)
}
}
}
}.awaitAll()
.minBy { if (it.isEmpty()) Int.MAX_VALUE else it.size }
return results
}
/**
* Fetch related mangas found in manga's page which is provided by sites.
* If using this, must also: 'override val supportsRelatedMangas = true'
*/
@Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getRelatedMangaList")) @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getRelatedMangaList"))
override fun fetchRelatedMangaList(manga: SManga): Observable<List<SManga>> { override fun fetchRelatedMangaList(manga: SManga): Observable<List<SManga>> {
return client.newCall(relatedMangaListRequest(manga)) return client.newCall(relatedMangaListRequest(manga))
@ -300,6 +364,7 @@ abstract class HttpSource : CatalogueSource {
/** /**
* Returns the request for get related manga list. Override only if it's needed to override * Returns the request for get related manga list. Override only if it's needed to override
* the url, send different headers or request method like POST. * the url, send different headers or request method like POST.
* If using this, must also: 'override val supportsRelatedMangas = true'
* *
* @param manga the manga to look for related mangas. * @param manga the manga to look for related mangas.
*/ */
@ -309,17 +374,11 @@ abstract class HttpSource : CatalogueSource {
/** /**
* Parses the response from the site and returns a list of related mangas. * Parses the response from the site and returns a list of related mangas.
* If using this, must also: 'override val supportsRelatedMangas = true'
* *
* @param response the response from the site. * @param response the response from the site.
*/ */
protected open fun relatedMangaListParse(response: Response): List<SManga> = throw UnsupportedOperationException() protected open fun relatedMangaListParse(response: Response): List<SManga> = popularMangaParse(response).mangas
/**
* Parses the response from the site and returns a SManga Object.
*
* @param response the response from the site.
*/
protected open fun relatedMangaPageParse(response: Response): SManga = throw UnsupportedOperationException()
// KMK <-- // KMK <--
/** /**

View file

@ -148,21 +148,24 @@ abstract class ParsedHttpSource : HttpSource() {
// KMK --> // KMK -->
/** /**
* Parses the response from the site and returns a list of related mangas. * Parses the response from the site and returns a list of related mangas.
* If using this, must also: 'override val supportsRelatedMangas = true'
* *
* @param response the response from the site. * @param response the response from the site.
*/ */
override fun relatedMangaListParse(response: Response): List<SManga> { override fun relatedMangaListParse(response: Response): List<SManga> {
val document = response.asJsoup() return response.asJsoup()
return document.select(relatedMangaListSelector()).map { relatedMangaFromElement(it) } .select(relatedMangaListSelector()).map { relatedMangaFromElement(it) }
} }
/** /**
* Returns the Jsoup selector that returns a list of [Element] corresponding to each related mangas. * Returns the Jsoup selector that returns a list of [Element] corresponding to each related mangas.
* If using this, must also: 'override val supportsRelatedMangas = true'
*/ */
protected open fun relatedMangaListSelector(): String = popularMangaSelector() protected open fun relatedMangaListSelector(): String = popularMangaSelector()
/** /**
* Returns a manga from the given element. * Returns a manga from the given element.
* If using this, must also: 'override val supportsRelatedMangas = true'
* *
* @param element an element obtained from [relatedMangaListSelector]. * @param element an element obtained from [relatedMangaListSelector].
*/ */