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 {
withIOContext {
if (state.source !is MergedSource) {
val relatedMangas = if (state.source.supportsRelatedMangas)
val relatedMangas = if (true)//state.source.supportsRelatedMangas)
withIOContext {
state.source.getRelatedMangaList(state.manga.toSManga())
.map {

View file

@ -49,13 +49,6 @@ interface Source {
}
// 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.
*

View file

@ -16,6 +16,10 @@ import eu.kanade.tachiyomi.source.model.SManga
import exh.log.maybeInjectEHLogger
import exh.pref.DelegateSourcePreferences
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.OkHttpClient
import okhttp3.Request
@ -27,6 +31,7 @@ import uy.kohesive.injekt.api.get
import java.net.URI
import java.net.URISyntaxException
import java.security.MessageDigest
import java.util.Locale
/**
* A simple implementation for sources from a website.
@ -275,6 +280,14 @@ abstract class HttpSource : CatalogueSource {
protected abstract fun mangaDetailsParse(response: Response): SManga
// 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.
* Normally it's not needed to override this method.
@ -285,9 +298,60 @@ abstract class HttpSource : CatalogueSource {
*/
@Suppress("DEPRECATION")
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"))
override fun fetchRelatedMangaList(manga: SManga): Observable<List<SManga>> {
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
* 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.
*/
@ -309,17 +374,11 @@ abstract class HttpSource : CatalogueSource {
/**
* 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.
*/
protected open fun relatedMangaListParse(response: Response): List<SManga> = throw UnsupportedOperationException()
/**
* 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()
protected open fun relatedMangaListParse(response: Response): List<SManga> = popularMangaParse(response).mangas
// KMK <--
/**

View file

@ -148,21 +148,24 @@ abstract class ParsedHttpSource : HttpSource() {
// KMK -->
/**
* 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.
*/
override fun relatedMangaListParse(response: Response): List<SManga> {
val document = response.asJsoup()
return document.select(relatedMangaListSelector()).map { relatedMangaFromElement(it) }
return response.asJsoup()
.select(relatedMangaListSelector()).map { relatedMangaFromElement(it) }
}
/**
* 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()
/**
* Returns a manga from the given element.
* If using this, must also: 'override val supportsRelatedMangas = true'
*
* @param element an element obtained from [relatedMangaListSelector].
*/