Skip to content

Commit

Permalink
ReaperScans Unoriginal: Moved to ParsedHttpSource (#7002)
Browse files Browse the repository at this point in the history
* move to ParsedHttpSource

* remove old code

* clean up
  • Loading branch information
dngonz authored Jan 6, 2025
1 parent 6f8e447 commit 1467ce3
Show file tree
Hide file tree
Showing 3 changed files with 274 additions and 9 deletions.
4 changes: 1 addition & 3 deletions src/en/reaperscansunoriginal/build.gradle
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
ext {
extName = 'Reaper Scans (unoriginal)'
extClass = '.ReaperScansUnoriginal'
themePkg = 'mangathemesia'
baseUrl = 'https://reaper-scans.com'
overrideVersionCode = 0
extVersionCode = 31
}

apply from: "$rootDir/common.gradle"
Original file line number Diff line number Diff line change
@@ -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"))
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,174 @@
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.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()
.rateLimit(3)
.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"))
}
}
}

0 comments on commit 1467ce3

Please sign in to comment.