Skip to content

Commit 1467ce3

Browse files
authored
ReaperScans Unoriginal: Moved to ParsedHttpSource (#7002)
* move to ParsedHttpSource * remove old code * clean up
1 parent 6f8e447 commit 1467ce3

File tree

3 files changed

+274
-9
lines changed

3 files changed

+274
-9
lines changed
Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
ext {
22
extName = 'Reaper Scans (unoriginal)'
33
extClass = '.ReaperScansUnoriginal'
4-
themePkg = 'mangathemesia'
5-
baseUrl = 'https://reaper-scans.com'
6-
overrideVersionCode = 0
4+
extVersionCode = 31
75
}
86

97
apply from: "$rootDir/common.gradle"
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package eu.kanade.tachiyomi.extension.en.reaperscansunoriginal
2+
3+
import eu.kanade.tachiyomi.source.model.Filter
4+
import eu.kanade.tachiyomi.source.model.FilterList
5+
import okhttp3.HttpUrl
6+
7+
interface UrlPartFilter {
8+
fun addUrlParameter(url: HttpUrl.Builder)
9+
}
10+
11+
abstract class SelectFilter(
12+
name: String,
13+
private val urlParameter: String,
14+
private val options: List<Pair<String, String>>,
15+
defaultValue: String? = null,
16+
) : UrlPartFilter, Filter.Select<String>(
17+
name,
18+
options.map { it.first }.toTypedArray(),
19+
options.indexOfFirst { it.second == defaultValue }.coerceAtLeast(0),
20+
) {
21+
override fun addUrlParameter(url: HttpUrl.Builder) {
22+
url.addQueryParameter(urlParameter, options[state].second)
23+
}
24+
}
25+
26+
class CheckBoxFilter(name: String, val value: String) : Filter.CheckBox(name)
27+
28+
open class CheckBoxGroup(
29+
name: String,
30+
private val urlParameter: String,
31+
options: List<Pair<String, String>>,
32+
) : UrlPartFilter, Filter.Group<CheckBoxFilter>(
33+
name,
34+
options.map { CheckBoxFilter(it.first, it.second) },
35+
) {
36+
override fun addUrlParameter(url: HttpUrl.Builder) {
37+
val checked = state.filter { it.state }.map { it.value }
38+
39+
if (checked.isNotEmpty()) {
40+
checked.forEach { genre ->
41+
url.addQueryParameter(urlParameter, genre)
42+
}
43+
}
44+
}
45+
}
46+
47+
class TypeFilter : CheckBoxGroup(
48+
"Status",
49+
"type[]",
50+
listOf(
51+
Pair("Action", "action"),
52+
Pair("Adventure", "adventure"),
53+
Pair("Fantasy", "fantasy"),
54+
Pair("Manga", "manga"),
55+
Pair("Manhua", "manhua"),
56+
Pair("Manhwa", "manhwa"),
57+
Pair("Seinen", "seinen"),
58+
),
59+
)
60+
61+
class GenreFilter(genres: List<Pair<String, String>>) : CheckBoxGroup(
62+
"Genres",
63+
"genre[]",
64+
genres,
65+
)
66+
67+
class YearFilter : CheckBoxGroup(
68+
"Status",
69+
"release[]",
70+
listOf(
71+
Pair("2024", "2024"),
72+
Pair("2023", "2023"),
73+
Pair("2022", "2022"),
74+
Pair("2021", "2021"),
75+
Pair("2020", "2020"),
76+
Pair("2019", "2019"),
77+
Pair("2018", "2018"),
78+
Pair("2017", "2017"),
79+
Pair("2016", "2016"),
80+
Pair("2015", "2015"),
81+
),
82+
)
83+
84+
class StatusFilter : CheckBoxGroup(
85+
"Status",
86+
"status[]",
87+
listOf(
88+
Pair("Releasing", "on-going"),
89+
Pair("Completed", "end"),
90+
),
91+
)
92+
93+
class OrderFilter(default: String? = null) : SelectFilter(
94+
"Sort by",
95+
"sort",
96+
listOf(
97+
Pair("", ""),
98+
Pair("Popular", "most_viewed"),
99+
Pair("Latest", "recently_added"),
100+
),
101+
default,
102+
) {
103+
companion object {
104+
val POPULAR = FilterList(OrderFilter("most_viewed"))
105+
val LATEST = FilterList(OrderFilter("recently_added"))
106+
}
107+
}
Lines changed: 166 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,174 @@
11
package eu.kanade.tachiyomi.extension.en.reaperscansunoriginal
22

3-
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
3+
import eu.kanade.tachiyomi.network.GET
44
import eu.kanade.tachiyomi.network.interceptor.rateLimit
5+
import eu.kanade.tachiyomi.source.model.Filter
6+
import eu.kanade.tachiyomi.source.model.FilterList
7+
import eu.kanade.tachiyomi.source.model.MangasPage
8+
import eu.kanade.tachiyomi.source.model.Page
9+
import eu.kanade.tachiyomi.source.model.SChapter
10+
import eu.kanade.tachiyomi.source.model.SManga
11+
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
12+
import eu.kanade.tachiyomi.util.asJsoup
13+
import okhttp3.HttpUrl.Companion.toHttpUrl
14+
import okhttp3.Request
15+
import okhttp3.Response
16+
import org.jsoup.nodes.Document
17+
import org.jsoup.nodes.Element
18+
import java.util.Calendar
19+
20+
class ReaperScansUnoriginal : ParsedHttpSource() {
21+
override val baseUrl = "https://reaper-scans.com"
22+
23+
override val name = "Reaper Scans (unoriginal)"
24+
25+
override val lang = "en"
26+
27+
override val supportsLatest = true
528

6-
class ReaperScansUnoriginal : MangaThemesia(
7-
"Reaper Scans (unoriginal)",
8-
"https://reaper-scans.com",
9-
"en",
10-
) {
1129
override val client = super.client.newBuilder()
1230
.rateLimit(3)
1331
.build()
32+
33+
// Popular
34+
override fun popularMangaFromElement(element: Element) = throw UnsupportedOperationException()
35+
36+
override fun popularMangaNextPageSelector(): String = throw UnsupportedOperationException()
37+
38+
override fun popularMangaRequest(page: Int) =
39+
searchMangaRequest(page, "", OrderFilter.POPULAR)
40+
41+
override fun popularMangaSelector() = throw UnsupportedOperationException()
42+
43+
override fun popularMangaParse(response: Response) = searchMangaParse(response)
44+
45+
// Latest
46+
override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException()
47+
48+
override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException()
49+
50+
override fun latestUpdatesRequest(page: Int) =
51+
searchMangaRequest(page, "", OrderFilter.LATEST)
52+
53+
override fun latestUpdatesSelector() = throw UnsupportedOperationException()
54+
55+
override fun latestUpdatesParse(response: Response) = searchMangaParse(response)
56+
57+
// Search
58+
override fun searchMangaFromElement(element: Element) = SManga.create().apply {
59+
thumbnail_url = element.select(".poster-image-wrapper > img").attr("src")
60+
title = element.select(".info > a").text()
61+
setUrlWithoutDomain(element.selectFirst(".info a")!!.attr("href"))
62+
}
63+
64+
override fun searchMangaNextPageSelector() = "a[rel=\"next\"]"
65+
66+
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
67+
val url = baseUrl.toHttpUrl().newBuilder().apply {
68+
addQueryParameter("post_type", "wp-manga")
69+
addQueryParameter("s", query)
70+
filters.filterIsInstance<UrlPartFilter>().forEach {
71+
it.addUrlParameter(this)
72+
}
73+
if (page > 1) {
74+
addPathSegment("page")
75+
addPathSegment(page.toString())
76+
}
77+
}.build()
78+
79+
return GET(url, headers)
80+
}
81+
82+
override fun searchMangaSelector() = ".inner"
83+
84+
override fun searchMangaParse(response: Response): MangasPage {
85+
if (genres.isEmpty()) {
86+
genres = parseGenres(response.asJsoup(response.peekBody(Long.MAX_VALUE).string()))
87+
}
88+
89+
return super.searchMangaParse(response)
90+
}
91+
92+
// Chapter
93+
override fun chapterFromElement(element: Element) = SChapter.create().apply {
94+
setUrlWithoutDomain(element.attr("href"))
95+
name = element.attr("title")
96+
date_upload = parseRelativeDate(element.selectFirst("span + span")?.text())
97+
}
98+
99+
private fun parseRelativeDate(date: String?): Long {
100+
if (date == null) {
101+
return 0L
102+
}
103+
104+
val trimmedDate = date.split(" ")
105+
if (trimmedDate.size != 3 && trimmedDate[2] != "ago") return 0L
106+
val number = trimmedDate[0].toIntOrNull() ?: return 0L
107+
val unit = trimmedDate[1].removeSuffix("s") // Remove 's' suffix
108+
val now = Calendar.getInstance()
109+
110+
val javaUnit = when (unit) {
111+
"year", "yr" -> Calendar.YEAR
112+
"month" -> Calendar.MONTH
113+
"week", "wk" -> Calendar.WEEK_OF_MONTH
114+
"day" -> Calendar.DAY_OF_MONTH
115+
"hour", "hr" -> Calendar.HOUR
116+
"minute", "min" -> Calendar.MINUTE
117+
"second", "sec" -> Calendar.SECOND
118+
else -> return 0L
119+
}
120+
121+
now.add(javaUnit, -number)
122+
123+
return now.timeInMillis
124+
}
125+
126+
override fun chapterListSelector() = "a.cairo"
127+
128+
// Details
129+
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
130+
document.selectFirst("div.serie-info")?.let { info ->
131+
description = info.selectFirst("div.description-content")?.text()
132+
author = info.selectFirst("span:containsOwn(Author) + span")?.text()
133+
artist = info.selectFirst("span:containsOwn(Artist) + span")?.text()
134+
status = info.selectFirst("span:containsOwn(Status) + span")?.text().toStatus()
135+
genre = info.select("div.genre-link").joinToString { it.text() }
136+
}
137+
}
138+
139+
private fun String?.toStatus() = when {
140+
this == null -> SManga.UNKNOWN
141+
this.contains("Ongoing") -> SManga.ONGOING
142+
this.contains("Completed") -> SManga.COMPLETED
143+
else -> SManga.UNKNOWN
144+
}
145+
146+
// Pages
147+
override fun pageListParse(document: Document): List<Page> {
148+
val chapterUrl = document.location()
149+
return document.select("div.image-skeleton img")
150+
.filterNot { it.attr("data-src").isEmpty() }
151+
.mapIndexed { i, img -> Page(i, chapterUrl, img.attr("data-src")) }
152+
}
153+
154+
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException()
155+
156+
// Filter
157+
override fun getFilterList() = FilterList(
158+
TypeFilter(),
159+
Filter.Header("Press \"Reset\" to attempt to load genres"),
160+
GenreFilter(genres),
161+
YearFilter(),
162+
StatusFilter(),
163+
OrderFilter(),
164+
)
165+
166+
private var genres = emptyList<Pair<String, String>>()
167+
168+
private fun parseGenres(document: Document): List<Pair<String, String>> {
169+
return document.select("li:has(input[name=\"genre[]\"])")
170+
.map {
171+
Pair(it.selectFirst("label")!!.text(), it.selectFirst("input")!!.attr("value"))
172+
}
173+
}
14174
}

0 commit comments

Comments
 (0)