AnimeSama: add filtering by genre (#10059)

* Update AnimeSama

Fix description & genre by updating html tag id

* Update build.gradle

Increasing the code version number

* Adding latest items for AnimeSama

only the items available on the main page

* Fix URL on latest items

* AnimeSama: adding genre filter

Using almost the same code as FuzzyDoodle, I added the only filter available on animesama: genre

* Update AnimeSama.kt

remove lint errors

* Including code review optimization

Co-authored-by: stevenyomi <95685115+stevenyomi@users.noreply.github.com>

* Fix build error

---------

Co-authored-by: stevenyomi <95685115+stevenyomi@users.noreply.github.com>
This commit is contained in:
Yakoo 2025-08-11 04:49:17 +02:00 committed by Draff
parent c2317eeeed
commit 8c7c46e0e2
Signed by: Draff
GPG Key ID: E8A89F3211677653
3 changed files with 98 additions and 13 deletions

View File

@ -1,7 +1,7 @@
ext {
extName = 'AnimeSama'
extClass = '.AnimeSama'
extVersionCode = 6
extVersionCode = 7
}
apply from: "$rootDir/common.gradle"

View File

@ -1,12 +1,17 @@
package eu.kanade.tachiyomi.extension.fr.animesama
import android.util.Log
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
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.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
@ -33,13 +38,55 @@ class AnimeSama : ParsedHttpSource() {
override fun headersBuilder(): Headers.Builder = super.headersBuilder()
.add("Accept-Language", "fr-FR")
// filters
private var genreList = listOf<Pair<String, String>>()
private var fetchFilterAttempts = 0
private suspend fun fetchFilters() {
if (fetchFilterAttempts < 3 && (genreList.isEmpty())) {
try {
val response = client.newCall(filtersRequest()).await().asJsoup()
parseFilters(response)
} catch (e: Exception) {
Log.e("$name: Filters", e.stackTraceToString())
}
fetchFilterAttempts++
}
}
private fun filtersRequest() = GET("$baseUrl/catalogue", headers)
private fun parseFilters(document: Document) {
genreList = document.select("#list_genres label").mapNotNull { labelElement ->
val input = labelElement.selectFirst("input[name=genre[]]") ?: return@mapNotNull null
val labelText = labelElement.ownText()
val value = input.attr("value")
labelText to value
}
}
override fun getFilterList(): FilterList {
val filters = mutableListOf<Filter<*>>()
launchIO { fetchFilters() }
if (genreList.isNotEmpty()) {
filters.add(GenreFilter(genreList))
}
if (filters.size < 1) {
filters.add(0, Filter.Header("Press 'reset' to load more filters"))
}
return FilterList(filters)
}
private fun launchIO(block: suspend () -> Unit) = GlobalScope.launch { block() }
// Popular
override fun popularMangaRequest(page: Int): Request {
val url = "$baseUrl/catalogue".toHttpUrl().newBuilder()
.addQueryParameter("type[0]", "Scans")
.addQueryParameter("page", page.toString())
.build()
return GET(url, headers)
}
@ -78,6 +125,11 @@ class AnimeSama : ParsedHttpSource() {
.addQueryParameter("type[0]", "Scans")
.addQueryParameter("search", query)
.addQueryParameter("page", page.toString())
.apply {
filters.filterIsInstance<UrlPartFilter>().forEach {
it.addUrlParameter(this)
}
}
.build()
return GET(url, headers)
@ -100,14 +152,14 @@ class AnimeSama : ParsedHttpSource() {
override fun chapterFromElement(element: Element): SChapter = throw UnsupportedOperationException()
fun String.containsMultipleTimes(search: String): Boolean {
private fun String.containsMultipleTimes(search: String): Boolean {
val regex = Regex(search)
val matches = regex.findAll(this)
val count = matches.count()
return count > 1
}
private fun parseChapterFromResponse(response: Response, translation_name: String): List<SChapter> {
private fun parseChapterFromResponse(response: Response, translationName: String): List<SChapter> {
val document = response.asJsoup()
val chapterUrl = document.baseUri().toHttpUrl()
@ -146,7 +198,7 @@ class AnimeSama : ParsedHttpSource() {
SChapter.create().apply {
name = "Chapitre $i"
setUrlWithoutDomain(chapterUrl.newBuilder().addQueryParameter("id", (parsedChapterList.size + 1).toString()).build().toString())
scanlator = translation_name
scanlator = translationName
},
)
}
@ -157,7 +209,7 @@ class AnimeSama : ParsedHttpSource() {
SChapter.create().apply {
name = "Chapitre $title"
setUrlWithoutDomain(chapterUrl.newBuilder().addQueryParameter("id", (parsedChapterList.size + 1).toString()).build().toString())
scanlator = translation_name
scanlator = translationName
},
)
chapterDelay++
@ -166,12 +218,12 @@ class AnimeSama : ParsedHttpSource() {
}
}
}
for (index in parsedChapterList.size..parsedJavascriptFileToJson.size - 1) {
for (index in parsedChapterList.size until parsedJavascriptFileToJson.size) {
parsedChapterList.add(
SChapter.create().apply {
name = "Chapitre " + (parsedChapterList.size + 1 - chapterDelay)
setUrlWithoutDomain(chapterUrl.newBuilder().addQueryParameter("id", (parsedChapterList.size + 1).toString()).build().toString())
scanlator = translation_name
scanlator = translationName
},
)
}
@ -222,7 +274,7 @@ class AnimeSama : ParsedHttpSource() {
val documentString = document.body().toString()
val allChapters: Map<Int, Int> = Regex("""eps(\d+)\s*(?:=\s*\[(.*?)\]|\.length\s*=\s*(\d+))""")
val allChapters: Map<Int, Int> = Regex("""eps(\d+)\s*(?:=\s*\[(.*?)]|\.length\s*=\s*(\d+))""")
.findAll(documentString)
.associate { match ->
val episode = match.groupValues[1].toInt()
@ -238,16 +290,16 @@ class AnimeSama : ParsedHttpSource() {
episode to length
}
val chapterSize = allChapters.get(chapter?.toInt()) ?: 1
val chapterSize = allChapters[chapter?.toInt()] ?: 1
val image_list = mutableListOf<Page>()
val imageList = mutableListOf<Page>()
for (index in 1 until chapterSize + 1) {
image_list.add(
imageList.add(
Page(index, imageUrl = "$cdn$title/$chapter/$index.jpg"),
)
}
return image_list
return imageList
}
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException()

View File

@ -0,0 +1,33 @@
package eu.kanade.tachiyomi.extension.fr.animesama
import eu.kanade.tachiyomi.source.model.Filter
import okhttp3.HttpUrl
interface UrlPartFilter {
fun addUrlParameter(url: HttpUrl.Builder)
}
class CheckBoxFilter(name: String, val value: String) : Filter.CheckBox(name)
abstract class CheckBoxGroup(
name: String,
options: List<Pair<String, String>>,
private val urlParameter: String,
) : UrlPartFilter, Filter.Group<CheckBoxFilter>(
name,
options.map { CheckBoxFilter(it.first, it.second) },
) {
override fun addUrlParameter(url: HttpUrl.Builder) {
state.filter { it.state }.forEach {
url.addQueryParameter(urlParameter, it.value)
}
}
}
class GenreFilter(
options: List<Pair<String, String>>,
) : CheckBoxGroup(
"Genres",
options,
"genre[0]",
)