Simple related manga search implementation

pull/203/head
Koitharu 3 years ago
parent 240562037d
commit b65e1c498e
No known key found for this signature in database
GPG Key ID: 8E861F8CE6E7CE27

@ -8,6 +8,7 @@ import org.koitharu.kotatsu.parsers.network.OkHttpWebClient
import org.koitharu.kotatsu.parsers.network.UserAgents
import org.koitharu.kotatsu.parsers.network.WebClient
import org.koitharu.kotatsu.parsers.util.FaviconParser
import org.koitharu.kotatsu.parsers.util.RelatedMangaFinder
import org.koitharu.kotatsu.parsers.util.domain
import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl
import java.util.*
@ -127,4 +128,14 @@ abstract class MangaParser @InternalParsersApi constructor(
open fun onCreateConfig(keys: MutableCollection<ConfigKey<*>>) {
keys.add(configKeyDomain)
}
open suspend fun getRelatedManga(seed: Manga): List<Manga> {
return RelatedMangaFinder(listOf(this)).invoke(seed)
}
protected fun getParser(source: MangaSource) = if (this.source == source) {
this
} else {
context.newParserInstance(source)
}
}

@ -76,6 +76,7 @@ internal abstract class GroupleParser(
mapOf(
"q" to query.urlEncoded(),
"offset" to (offset upBy PAGE_SIZE_SEARCH).toString(),
"fast-filter" to "CREATION",
),
)
@ -321,6 +322,15 @@ internal abstract class GroupleParser(
keys.add(userAgentKey)
}
override suspend fun getRelatedManga(seed: Manga): List<Manga> {
val parsers = listOf(
getParser(MangaSource.READMANGA_RU),
getParser(MangaSource.MINTMANGA),
getParser(MangaSource.SELFMANGA),
)
return RelatedMangaFinder(parsers).invoke(seed)
}
private fun getSortKey(sortOrder: SortOrder) =
when (sortOrder) {
SortOrder.ALPHABETICAL -> "name"

@ -8,7 +8,7 @@ import org.koitharu.kotatsu.parsers.site.madara.MadaraParser
@MangaSourceParser("ATLANTISSCAN", "Atlantisscan", "pt")
internal class Atlantisscan(context: MangaLoaderContext) :
MadaraParser(context, MangaSource.ATLANTISSCAN, "atlantisscan.com") {
MadaraParser(context, MangaSource.ATLANTISSCAN, "br.atlantisscan.com", pageSize = 10) {
override val datePattern = "dd/MM/yyyy"

@ -0,0 +1,47 @@
package org.koitharu.kotatsu.parsers.util
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.model.Manga
class RelatedMangaFinder(
private val parsers: Collection<MangaParser>,
) {
private val regexWhitespace = Regex("\\s+")
suspend operator fun invoke(seed: Manga): List<Manga> = coroutineScope {
parsers.singleOrNull()?.let { parser ->
findRelatedImpl(this, parser, seed)
} ?: parsers.map { parser ->
async {
findRelatedImpl(this, parser, seed)
}
}.awaitAll().flatten()
}
private suspend fun findRelatedImpl(scope: CoroutineScope, parser: MangaParser, seed: Manga): List<Manga> {
val words = HashSet<String>()
words += seed.title.split(regexWhitespace)
seed.altTitle?.let {
words += it.split(regexWhitespace)
}
if (words.isEmpty()) {
return emptyList()
}
val results = words.map { keyword ->
scope.async {
val result = parser.getList(0, keyword)
result.filter { it.id != seed.id && it.containKeyword(keyword) }
}
}.awaitAll()
return results.minBy { if (it.isEmpty()) Int.MAX_VALUE else it.size }
}
private fun Manga.containKeyword(keyword: String): Boolean {
return title.contains(keyword, ignoreCase = true) || altTitle?.contains(keyword, ignoreCase = true) == true
}
}

@ -2,6 +2,7 @@
package org.koitharu.kotatsu.parsers.util
import androidx.annotation.FloatRange
import androidx.collection.arraySetOf
import java.math.BigInteger
import java.net.URLEncoder
@ -209,6 +210,18 @@ fun String.levenshteinDistance(other: String): Int {
return cost[lhsLength - 1]
}
/**
* @param threshold 0 = exact match
*/
fun String.almostEquals(other: String, @FloatRange(from = 0.0) threshold: Float): Boolean {
if (threshold <= 0f) {
return equals(other, ignoreCase = true)
}
val diff = lowercase().levenshteinDistance(other.lowercase()) / ((length + other.length) / 2f)
return diff < threshold
}
inline fun <T> Appendable.appendAll(
items: Iterable<T>,
separator: CharSequence,
@ -223,4 +236,4 @@ inline fun <T> Appendable.appendAll(
}
append(transform(item))
}
}
}

@ -3,6 +3,7 @@ package org.koitharu.kotatsu.parsers
import kotlinx.coroutines.test.runTest
import okhttp3.HttpUrl
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.params.ParameterizedTest
import org.koitharu.kotatsu.parsers.model.Manga
@ -22,6 +23,14 @@ internal class MangaParserTest {
private val context = MangaLoaderContextMock
@Test
fun related() = runTest {
val parser = context.newParserInstance(MangaSource.READMANGA_RU)
val seed = parser.getList(0, "emanon").first()
val related = parser.getRelatedManga(seed)
assert(related.isNotEmpty() && seed !in related)
}
@ParameterizedTest(name = "{index}|list|{0}")
@MangaSources
fun list(source: MangaSource) = runTest {

Loading…
Cancel
Save