ReaperScans Unoriginal: Moved to ParsedHttpSource (#7002)

* move to ParsedHttpSource

* remove old code

* clean up
This commit is contained in:
dngonz 2025-01-06 14:16:23 +01:00 committed by Draff
parent c88b8b28aa
commit ad73175a1f
No known key found for this signature in database
GPG Key ID: E8A89F3211677653
3 changed files with 274 additions and 9 deletions

View File

@ -1,9 +1,7 @@
ext { ext {
extName = 'Reaper Scans (unoriginal)' extName = 'Reaper Scans (unoriginal)'
extClass = '.ReaperScansUnoriginal' extClass = '.ReaperScansUnoriginal'
themePkg = 'mangathemesia' extVersionCode = 31
baseUrl = 'https://reaper-scans.com'
overrideVersionCode = 0
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View File

@ -0,0 +1,107 @@
package eu.kanade.tachiyomi.extension.en.reaperscansunoriginal
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import okhttp3.HttpUrl
interface UrlPartFilter {
fun addUrlParameter(url: HttpUrl.Builder)
}
abstract class SelectFilter(
name: String,
private val urlParameter: String,
private val options: List<Pair<String, String>>,
defaultValue: String? = null,
) : UrlPartFilter, Filter.Select<String>(
name,
options.map { it.first }.toTypedArray(),
options.indexOfFirst { it.second == defaultValue }.coerceAtLeast(0),
) {
override fun addUrlParameter(url: HttpUrl.Builder) {
url.addQueryParameter(urlParameter, options[state].second)
}
}
class CheckBoxFilter(name: String, val value: String) : Filter.CheckBox(name)
open class CheckBoxGroup(
name: String,
private val urlParameter: String,
options: List<Pair<String, String>>,
) : UrlPartFilter, Filter.Group<CheckBoxFilter>(
name,
options.map { CheckBoxFilter(it.first, it.second) },
) {
override fun addUrlParameter(url: HttpUrl.Builder) {
val checked = state.filter { it.state }.map { it.value }
if (checked.isNotEmpty()) {
checked.forEach { genre ->
url.addQueryParameter(urlParameter, genre)
}
}
}
}
class TypeFilter : CheckBoxGroup(
"Status",
"type[]",
listOf(
Pair("Action", "action"),
Pair("Adventure", "adventure"),
Pair("Fantasy", "fantasy"),
Pair("Manga", "manga"),
Pair("Manhua", "manhua"),
Pair("Manhwa", "manhwa"),
Pair("Seinen", "seinen"),
),
)
class GenreFilter(genres: List<Pair<String, String>>) : CheckBoxGroup(
"Genres",
"genre[]",
genres,
)
class YearFilter : CheckBoxGroup(
"Status",
"release[]",
listOf(
Pair("2024", "2024"),
Pair("2023", "2023"),
Pair("2022", "2022"),
Pair("2021", "2021"),
Pair("2020", "2020"),
Pair("2019", "2019"),
Pair("2018", "2018"),
Pair("2017", "2017"),
Pair("2016", "2016"),
Pair("2015", "2015"),
),
)
class StatusFilter : CheckBoxGroup(
"Status",
"status[]",
listOf(
Pair("Releasing", "on-going"),
Pair("Completed", "end"),
),
)
class OrderFilter(default: String? = null) : SelectFilter(
"Sort by",
"sort",
listOf(
Pair("", ""),
Pair("Popular", "most_viewed"),
Pair("Latest", "recently_added"),
),
default,
) {
companion object {
val POPULAR = FilterList(OrderFilter("most_viewed"))
val LATEST = FilterList(OrderFilter("recently_added"))
}
}

View File

@ -1,14 +1,174 @@
package eu.kanade.tachiyomi.extension.en.reaperscansunoriginal package eu.kanade.tachiyomi.extension.en.reaperscansunoriginal
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.interceptor.rateLimit import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
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 okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import java.util.Calendar
class ReaperScansUnoriginal : ParsedHttpSource() {
override val baseUrl = "https://reaper-scans.com"
override val name = "Reaper Scans (unoriginal)"
override val lang = "en"
override val supportsLatest = true
class ReaperScansUnoriginal : MangaThemesia(
"Reaper Scans (unoriginal)",
"https://reaper-scans.com",
"en",
) {
override val client = super.client.newBuilder() override val client = super.client.newBuilder()
.rateLimit(3) .rateLimit(3)
.build() .build()
// Popular
override fun popularMangaFromElement(element: Element) = throw UnsupportedOperationException()
override fun popularMangaNextPageSelector(): String = throw UnsupportedOperationException()
override fun popularMangaRequest(page: Int) =
searchMangaRequest(page, "", OrderFilter.POPULAR)
override fun popularMangaSelector() = throw UnsupportedOperationException()
override fun popularMangaParse(response: Response) = searchMangaParse(response)
// Latest
override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException()
override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException()
override fun latestUpdatesRequest(page: Int) =
searchMangaRequest(page, "", OrderFilter.LATEST)
override fun latestUpdatesSelector() = throw UnsupportedOperationException()
override fun latestUpdatesParse(response: Response) = searchMangaParse(response)
// Search
override fun searchMangaFromElement(element: Element) = SManga.create().apply {
thumbnail_url = element.select(".poster-image-wrapper > img").attr("src")
title = element.select(".info > a").text()
setUrlWithoutDomain(element.selectFirst(".info a")!!.attr("href"))
}
override fun searchMangaNextPageSelector() = "a[rel=\"next\"]"
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = baseUrl.toHttpUrl().newBuilder().apply {
addQueryParameter("post_type", "wp-manga")
addQueryParameter("s", query)
filters.filterIsInstance<UrlPartFilter>().forEach {
it.addUrlParameter(this)
}
if (page > 1) {
addPathSegment("page")
addPathSegment(page.toString())
}
}.build()
return GET(url, headers)
}
override fun searchMangaSelector() = ".inner"
override fun searchMangaParse(response: Response): MangasPage {
if (genres.isEmpty()) {
genres = parseGenres(response.asJsoup(response.peekBody(Long.MAX_VALUE).string()))
}
return super.searchMangaParse(response)
}
// Chapter
override fun chapterFromElement(element: Element) = SChapter.create().apply {
setUrlWithoutDomain(element.attr("href"))
name = element.attr("title")
date_upload = parseRelativeDate(element.selectFirst("span + span")?.text())
}
private fun parseRelativeDate(date: String?): Long {
if (date == null) {
return 0L
}
val trimmedDate = date.split(" ")
if (trimmedDate.size != 3 && trimmedDate[2] != "ago") return 0L
val number = trimmedDate[0].toIntOrNull() ?: return 0L
val unit = trimmedDate[1].removeSuffix("s") // Remove 's' suffix
val now = Calendar.getInstance()
val javaUnit = when (unit) {
"year", "yr" -> Calendar.YEAR
"month" -> Calendar.MONTH
"week", "wk" -> Calendar.WEEK_OF_MONTH
"day" -> Calendar.DAY_OF_MONTH
"hour", "hr" -> Calendar.HOUR
"minute", "min" -> Calendar.MINUTE
"second", "sec" -> Calendar.SECOND
else -> return 0L
}
now.add(javaUnit, -number)
return now.timeInMillis
}
override fun chapterListSelector() = "a.cairo"
// Details
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
document.selectFirst("div.serie-info")?.let { info ->
description = info.selectFirst("div.description-content")?.text()
author = info.selectFirst("span:containsOwn(Author) + span")?.text()
artist = info.selectFirst("span:containsOwn(Artist) + span")?.text()
status = info.selectFirst("span:containsOwn(Status) + span")?.text().toStatus()
genre = info.select("div.genre-link").joinToString { it.text() }
}
}
private fun String?.toStatus() = when {
this == null -> SManga.UNKNOWN
this.contains("Ongoing") -> SManga.ONGOING
this.contains("Completed") -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
// Pages
override fun pageListParse(document: Document): List<Page> {
val chapterUrl = document.location()
return document.select("div.image-skeleton img")
.filterNot { it.attr("data-src").isEmpty() }
.mapIndexed { i, img -> Page(i, chapterUrl, img.attr("data-src")) }
}
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException()
// Filter
override fun getFilterList() = FilterList(
TypeFilter(),
Filter.Header("Press \"Reset\" to attempt to load genres"),
GenreFilter(genres),
YearFilter(),
StatusFilter(),
OrderFilter(),
)
private var genres = emptyList<Pair<String, String>>()
private fun parseGenres(document: Document): List<Pair<String, String>> {
return document.select("li:has(input[name=\"genre[]\"])")
.map {
Pair(it.selectFirst("label")!!.text(), it.selectFirst("input")!!.attr("value"))
}
}
} }